@prom.codes/memory-mcp 0.4.0 → 0.5.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/dist/bin.js +161 -4
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -999,6 +999,79 @@ function delay(ms) {
|
|
|
999
999
|
return new Promise((r) => setTimeout(r, ms));
|
|
1000
1000
|
}
|
|
1001
1001
|
|
|
1002
|
+
// dist/rewrite.js
|
|
1003
|
+
var SYSTEM_PROMPT2 = "You help search an AI agent's long-term memory of a software project and its conversations. Given a question, write ONE short hypothetical memory entry (1\u20132 sentences, plain statements) that \u2014 if it existed \u2014 would directly answer it. Use concrete nouns, names and likely phrasings a developer would store. Do NOT answer that you don't know, do NOT add preamble or quotes \u2014 output only the hypothetical statement.";
|
|
1004
|
+
var OpenAICompatRewriter = class {
|
|
1005
|
+
name;
|
|
1006
|
+
model;
|
|
1007
|
+
#url;
|
|
1008
|
+
#apiKey;
|
|
1009
|
+
#maxRetries;
|
|
1010
|
+
#retryBaseMs;
|
|
1011
|
+
#maxTokens;
|
|
1012
|
+
#temperature;
|
|
1013
|
+
#fetch;
|
|
1014
|
+
constructor(opts) {
|
|
1015
|
+
this.name = opts.name;
|
|
1016
|
+
this.model = opts.model;
|
|
1017
|
+
this.#url = `${opts.baseUrl.replace(/\/+$/, "")}/chat/completions`;
|
|
1018
|
+
this.#apiKey = opts.apiKey;
|
|
1019
|
+
this.#maxRetries = opts.maxRetries ?? 2;
|
|
1020
|
+
this.#retryBaseMs = opts.retryBaseMs ?? 400;
|
|
1021
|
+
this.#maxTokens = opts.maxTokens ?? 160;
|
|
1022
|
+
this.#temperature = opts.temperature ?? 0;
|
|
1023
|
+
this.#fetch = opts.fetchImpl ?? fetch;
|
|
1024
|
+
}
|
|
1025
|
+
async rewrite(query, opts) {
|
|
1026
|
+
const trimmed = query.trim();
|
|
1027
|
+
if (trimmed === "")
|
|
1028
|
+
return [];
|
|
1029
|
+
const n = Math.max(1, opts?.n ?? 1);
|
|
1030
|
+
const body = JSON.stringify({
|
|
1031
|
+
model: this.model,
|
|
1032
|
+
temperature: this.#temperature,
|
|
1033
|
+
max_tokens: this.#maxTokens,
|
|
1034
|
+
n,
|
|
1035
|
+
messages: [
|
|
1036
|
+
{ role: "system", content: SYSTEM_PROMPT2 },
|
|
1037
|
+
{ role: "user", content: `Question: ${trimmed}` }
|
|
1038
|
+
]
|
|
1039
|
+
});
|
|
1040
|
+
const headers = { "content-type": "application/json" };
|
|
1041
|
+
if (this.#apiKey !== void 0 && this.#apiKey !== "") {
|
|
1042
|
+
headers.authorization = `Bearer ${this.#apiKey}`;
|
|
1043
|
+
}
|
|
1044
|
+
for (let attempt = 0; attempt <= this.#maxRetries; attempt++) {
|
|
1045
|
+
try {
|
|
1046
|
+
const res = await this.#fetch(this.#url, {
|
|
1047
|
+
method: "POST",
|
|
1048
|
+
headers,
|
|
1049
|
+
body,
|
|
1050
|
+
...opts?.signal ? { signal: opts.signal } : {}
|
|
1051
|
+
});
|
|
1052
|
+
if (res.status === 429 || res.status >= 500) {
|
|
1053
|
+
} else if (!res.ok) {
|
|
1054
|
+
return [];
|
|
1055
|
+
} else {
|
|
1056
|
+
const json = await res.json();
|
|
1057
|
+
const out = [];
|
|
1058
|
+
for (const c of json.choices ?? []) {
|
|
1059
|
+
const t = (c.message?.content ?? "").trim();
|
|
1060
|
+
if (t !== "")
|
|
1061
|
+
out.push(t.slice(0, 1e3));
|
|
1062
|
+
}
|
|
1063
|
+
return out;
|
|
1064
|
+
}
|
|
1065
|
+
} catch {
|
|
1066
|
+
}
|
|
1067
|
+
if (attempt < this.#maxRetries) {
|
|
1068
|
+
await new Promise((r) => setTimeout(r, this.#retryBaseMs * 2 ** attempt));
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
return [];
|
|
1072
|
+
}
|
|
1073
|
+
};
|
|
1074
|
+
|
|
1002
1075
|
// dist/sqlite.js
|
|
1003
1076
|
import { randomUUID } from "node:crypto";
|
|
1004
1077
|
import { mkdirSync } from "node:fs";
|
|
@@ -1227,6 +1300,7 @@ var SqliteMemoryBackend = class {
|
|
|
1227
1300
|
db;
|
|
1228
1301
|
embedder;
|
|
1229
1302
|
reranker;
|
|
1303
|
+
rewriter;
|
|
1230
1304
|
/** Record ids whose vector is missing/stale, awaiting a batched embed. */
|
|
1231
1305
|
pendingEmbed = /* @__PURE__ */ new Set();
|
|
1232
1306
|
closed = false;
|
|
@@ -1242,6 +1316,7 @@ var SqliteMemoryBackend = class {
|
|
|
1242
1316
|
this.db.exec(`INSERT INTO agent_memory_fts (agent_memory_fts) VALUES ('rebuild')`);
|
|
1243
1317
|
this.embedder = opts.embedder;
|
|
1244
1318
|
this.reranker = opts.reranker;
|
|
1319
|
+
this.rewriter = opts.rewriter;
|
|
1245
1320
|
if (this.embedder !== void 0)
|
|
1246
1321
|
this.queueUnembedded();
|
|
1247
1322
|
}
|
|
@@ -1343,11 +1418,12 @@ var SqliteMemoryBackend = class {
|
|
|
1343
1418
|
return [];
|
|
1344
1419
|
const finalLimit = input.limit ?? 20;
|
|
1345
1420
|
const poolLimit = Math.max(finalLimit * 4, 40);
|
|
1346
|
-
const
|
|
1421
|
+
const channelInput = await this.applyRewrite(input);
|
|
1422
|
+
const ftsHits = this.ftsSearch(channelInput, poolLimit);
|
|
1347
1423
|
let vecHits = [];
|
|
1348
1424
|
if (this.embedder !== void 0) {
|
|
1349
1425
|
try {
|
|
1350
|
-
vecHits = await this.vectorSearch(
|
|
1426
|
+
vecHits = await this.vectorSearch(channelInput, poolLimit);
|
|
1351
1427
|
} catch {
|
|
1352
1428
|
vecHits = [];
|
|
1353
1429
|
}
|
|
@@ -1366,6 +1442,24 @@ var SqliteMemoryBackend = class {
|
|
|
1366
1442
|
const reranked = input.rerank === false ? pool : await this.rerankPool(input.query, pool, finalLimit);
|
|
1367
1443
|
return reranked.slice(0, finalLimit);
|
|
1368
1444
|
}
|
|
1445
|
+
/**
|
|
1446
|
+
* M4: rewrite the query for the retrieval channels (HyDE concat). Returns the
|
|
1447
|
+
* input unchanged when no rewriter is configured or it errors/empties.
|
|
1448
|
+
*/
|
|
1449
|
+
async applyRewrite(input) {
|
|
1450
|
+
if (this.rewriter === void 0 || input.rewrite === false)
|
|
1451
|
+
return input;
|
|
1452
|
+
try {
|
|
1453
|
+
const docs = await this.rewriter.rewrite(input.query, { n: 1 });
|
|
1454
|
+
const hyp = docs[0]?.trim();
|
|
1455
|
+
if (hyp)
|
|
1456
|
+
return { ...input, query: `${input.query}
|
|
1457
|
+
|
|
1458
|
+
${hyp}` };
|
|
1459
|
+
} catch {
|
|
1460
|
+
}
|
|
1461
|
+
return input;
|
|
1462
|
+
}
|
|
1369
1463
|
/**
|
|
1370
1464
|
* Reorder a first-stage pool with the cross-encoder reranker, scoring each
|
|
1371
1465
|
* candidate's `key + value` jointly against the query. Returns the pool
|
|
@@ -1752,6 +1846,57 @@ function discoverMemoryExtractor(env) {
|
|
|
1752
1846
|
}
|
|
1753
1847
|
throw new Error(`unknown PROMETHEUS_MEMORY_EXTRACT_PROVIDER="${forced}" (expected "none", "mistral", "openai", or "generic")`);
|
|
1754
1848
|
}
|
|
1849
|
+
function discoverMemoryRewriter(env) {
|
|
1850
|
+
const forced = (env.PROMETHEUS_MEMORY_REWRITE_PROVIDER ?? "none").toLowerCase();
|
|
1851
|
+
if (forced === "" || forced === "none")
|
|
1852
|
+
return { id: "none", provider: null };
|
|
1853
|
+
if (forced === "mistral") {
|
|
1854
|
+
const apiKey = env.MISTRAL_API_KEY;
|
|
1855
|
+
if (apiKey === void 0 || apiKey === "") {
|
|
1856
|
+
throw new Error('PROMETHEUS_MEMORY_REWRITE_PROVIDER="mistral" requires MISTRAL_API_KEY.');
|
|
1857
|
+
}
|
|
1858
|
+
return {
|
|
1859
|
+
id: "mistral",
|
|
1860
|
+
provider: new OpenAICompatRewriter({
|
|
1861
|
+
name: "mistral-rewrite",
|
|
1862
|
+
model: env.PROMETHEUS_MEMORY_REWRITE_MODEL ?? "mistral-small-latest",
|
|
1863
|
+
baseUrl: env.MISTRAL_BASE_URL ?? "https://api.mistral.ai/v1",
|
|
1864
|
+
apiKey
|
|
1865
|
+
})
|
|
1866
|
+
};
|
|
1867
|
+
}
|
|
1868
|
+
if (forced === "openai") {
|
|
1869
|
+
const apiKey = env.OPENAI_API_KEY;
|
|
1870
|
+
if (apiKey === void 0 || apiKey === "") {
|
|
1871
|
+
throw new Error('PROMETHEUS_MEMORY_REWRITE_PROVIDER="openai" requires OPENAI_API_KEY.');
|
|
1872
|
+
}
|
|
1873
|
+
return {
|
|
1874
|
+
id: "openai",
|
|
1875
|
+
provider: new OpenAICompatRewriter({
|
|
1876
|
+
name: "openai-rewrite",
|
|
1877
|
+
model: env.PROMETHEUS_MEMORY_REWRITE_MODEL ?? "gpt-4o-mini",
|
|
1878
|
+
baseUrl: env.OPENAI_BASE_URL ?? "https://api.openai.com/v1",
|
|
1879
|
+
apiKey
|
|
1880
|
+
})
|
|
1881
|
+
};
|
|
1882
|
+
}
|
|
1883
|
+
if (forced === "generic" || forced === "openai-compat") {
|
|
1884
|
+
const baseUrl = env.PROMETHEUS_MEMORY_REWRITE_ENDPOINT;
|
|
1885
|
+
if (baseUrl === void 0 || baseUrl === "") {
|
|
1886
|
+
throw new Error(`PROMETHEUS_MEMORY_REWRITE_PROVIDER="${forced}" requires PROMETHEUS_MEMORY_REWRITE_ENDPOINT.`);
|
|
1887
|
+
}
|
|
1888
|
+
return {
|
|
1889
|
+
id: "generic",
|
|
1890
|
+
provider: new OpenAICompatRewriter({
|
|
1891
|
+
name: env.PROMETHEUS_MEMORY_REWRITE_NAME ?? "generic-rewrite",
|
|
1892
|
+
model: env.PROMETHEUS_MEMORY_REWRITE_MODEL ?? "default",
|
|
1893
|
+
baseUrl,
|
|
1894
|
+
...env.PROMETHEUS_MEMORY_REWRITE_API_KEY ? { apiKey: env.PROMETHEUS_MEMORY_REWRITE_API_KEY } : {}
|
|
1895
|
+
})
|
|
1896
|
+
};
|
|
1897
|
+
}
|
|
1898
|
+
throw new Error(`unknown PROMETHEUS_MEMORY_REWRITE_PROVIDER="${forced}" (expected "none", "mistral", "openai", or "generic")`);
|
|
1899
|
+
}
|
|
1755
1900
|
function composeFromEnv(opts) {
|
|
1756
1901
|
const env = opts.env;
|
|
1757
1902
|
const override = (opts.workspaceRootOverride ?? "").trim();
|
|
@@ -1765,9 +1910,11 @@ function composeFromEnv(opts) {
|
|
|
1765
1910
|
const { id: embedderId, embedder } = discoverMemoryEmbedder(env);
|
|
1766
1911
|
const { id: rerankerId, provider: reranker } = discoverMemoryReranker(env);
|
|
1767
1912
|
const { id: extractorId, provider: extractor } = discoverMemoryExtractor(env);
|
|
1913
|
+
const { id: rewriterId, provider: rewriter } = discoverMemoryRewriter(env);
|
|
1768
1914
|
const backend = new SqliteMemoryBackend(dbPath, {
|
|
1769
1915
|
...embedder !== void 0 ? { embedder } : {},
|
|
1770
|
-
...reranker !== null ? { reranker } : {}
|
|
1916
|
+
...reranker !== null ? { reranker } : {},
|
|
1917
|
+
...rewriter !== null ? { rewriter } : {}
|
|
1771
1918
|
});
|
|
1772
1919
|
return {
|
|
1773
1920
|
backend,
|
|
@@ -1781,6 +1928,8 @@ function composeFromEnv(opts) {
|
|
|
1781
1928
|
rerankerId,
|
|
1782
1929
|
extractor,
|
|
1783
1930
|
extractorId,
|
|
1931
|
+
rewriter,
|
|
1932
|
+
rewriterId,
|
|
1784
1933
|
close: () => backend.close()
|
|
1785
1934
|
};
|
|
1786
1935
|
}
|
|
@@ -2405,6 +2554,10 @@ var SERVER_IDENTITY = {
|
|
|
2405
2554
|
var SERVER_INSTRUCTIONS = "Persistent agent memory for this workspace. At the START of a session or task, call memory_read to recall facts, decisions and procedures from earlier sessions. When the user states a durable preference, decision or correction, store it with memory_write. Use memory_search for keyword recall when memory_read is not specific enough. At the END of a session, consolidate what was learned with memory_capture. Run memory_setup once per workspace to install the memory protocol into runtime rule files. Never store secrets, API keys or credentials \u2014 such writes are rejected.";
|
|
2406
2555
|
|
|
2407
2556
|
// dist/bin.js
|
|
2557
|
+
function looksLikeMissingNativeBinding(msg) {
|
|
2558
|
+
return /bindings file|better_sqlite3\.node|could not locate the bindings|node_module_version|was compiled against a different|invalid elf|\.node['"\s]/i.test(msg);
|
|
2559
|
+
}
|
|
2560
|
+
var NATIVE_BINDING_HINT = '\nThis looks like the native `better-sqlite3` module failed to load \u2014 usually\nbecause npm `ignore-scripts=true` (corporate hardening) made `npx` skip the\nnative build, so the binary was never produced. Fix it once, keeping your\nhardening intact:\n npm install -g @prom.codes/memory-mcp --ignore-scripts=false --foreground-scripts\nthen point Claude Code at the built binary instead of npx:\n claude mcp add memory -- node "$(npm root -g)/@prom.codes/memory-mcp/dist/bin.js"\nDocs: https://prom.codes/docs/mcp/claude-code\n';
|
|
2408
2561
|
async function main() {
|
|
2409
2562
|
const env = process.env;
|
|
2410
2563
|
const explicitRoot = (env.PROMETHEUS_WORKSPACE_ROOT ?? "").trim();
|
|
@@ -2434,7 +2587,7 @@ async function main() {
|
|
|
2434
2587
|
env,
|
|
2435
2588
|
...override !== void 0 && override !== "" ? { workspaceRootOverride: override } : {}
|
|
2436
2589
|
});
|
|
2437
|
-
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}
|
|
2590
|
+
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}
|
|
2438
2591
|
`);
|
|
2439
2592
|
registerTools(server, composed);
|
|
2440
2593
|
};
|
|
@@ -2455,6 +2608,8 @@ async function main() {
|
|
|
2455
2608
|
const message = err instanceof Error ? err.message : String(err);
|
|
2456
2609
|
process.stderr.write(`prometheus-memory-mcp: fatal during boot: ${message}
|
|
2457
2610
|
`);
|
|
2611
|
+
if (looksLikeMissingNativeBinding(message))
|
|
2612
|
+
process.stderr.write(NATIVE_BINDING_HINT);
|
|
2458
2613
|
process.exit(1);
|
|
2459
2614
|
}
|
|
2460
2615
|
};
|
|
@@ -2469,5 +2624,7 @@ main().catch((err) => {
|
|
|
2469
2624
|
const message = err instanceof Error ? err.message : String(err);
|
|
2470
2625
|
process.stderr.write(`prometheus-memory-mcp: fatal: ${message}
|
|
2471
2626
|
`);
|
|
2627
|
+
if (looksLikeMissingNativeBinding(message))
|
|
2628
|
+
process.stderr.write(NATIVE_BINDING_HINT);
|
|
2472
2629
|
process.exit(1);
|
|
2473
2630
|
});
|