virlow-mcp 3.69.0 → 3.70.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/cli.js +102 -21
- package/package.json +4 -4
package/dist/cli.js
CHANGED
|
@@ -2121,7 +2121,7 @@ function buildGraph(memories) {
|
|
|
2121
2121
|
const key = entityKey(name);
|
|
2122
2122
|
let entity = entities.get(key);
|
|
2123
2123
|
if (!entity) {
|
|
2124
|
-
entity = { key, name: name.trim(), memoryIds: /* @__PURE__ */ new Set(), edges: [] };
|
|
2124
|
+
entity = { key, name: name.trim(), memoryIds: /* @__PURE__ */ new Set(), mentionIds: /* @__PURE__ */ new Set(), edges: [] };
|
|
2125
2125
|
entities.set(key, entity);
|
|
2126
2126
|
spellings.set(key, /* @__PURE__ */ new Map());
|
|
2127
2127
|
}
|
|
@@ -2130,7 +2130,8 @@ function buildGraph(memories) {
|
|
|
2130
2130
|
counts.set(name.trim(), (counts.get(name.trim()) ?? 0) + 1);
|
|
2131
2131
|
return entity;
|
|
2132
2132
|
};
|
|
2133
|
-
|
|
2133
|
+
const all = [...memories];
|
|
2134
|
+
for (const memory of all) {
|
|
2134
2135
|
for (const name of memory.meta.about ?? [])
|
|
2135
2136
|
touch(name, memory.id);
|
|
2136
2137
|
for (const relation of memory.meta.relations ?? []) {
|
|
@@ -2148,7 +2149,16 @@ function buildGraph(memories) {
|
|
|
2148
2149
|
if (best)
|
|
2149
2150
|
entities.get(key).name = best[0];
|
|
2150
2151
|
}
|
|
2151
|
-
|
|
2152
|
+
const graph = { entities, edges };
|
|
2153
|
+
for (const memory of all) {
|
|
2154
|
+
for (const entity of entitiesMentioned(graph, `${memory.label} ${memory.text}`)) {
|
|
2155
|
+
if (entity.memoryIds.has(memory.id))
|
|
2156
|
+
continue;
|
|
2157
|
+
entity.memoryIds.add(memory.id);
|
|
2158
|
+
entity.mentionIds.add(memory.id);
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
return graph;
|
|
2152
2162
|
}
|
|
2153
2163
|
function isEntityLike(value) {
|
|
2154
2164
|
return /^[A-Z0-9]/.test(value.trim());
|
|
@@ -2227,7 +2237,8 @@ function supersededBy(graph, relation, at) {
|
|
|
2227
2237
|
});
|
|
2228
2238
|
}
|
|
2229
2239
|
function graphHealth(graph, memories, at = Date.now()) {
|
|
2230
|
-
const
|
|
2240
|
+
const mentioned = new Set([...graph.entities.values()].flatMap((e) => [...e.mentionIds]));
|
|
2241
|
+
const unlinked = memories.filter((m) => !(m.meta.about?.length || m.meta.relations?.length) && !mentioned.has(m.id)).map((m) => m.id);
|
|
2231
2242
|
const names = [...graph.entities.values()];
|
|
2232
2243
|
const possibleDuplicates = [];
|
|
2233
2244
|
for (let i = 0; i < names.length; i++) {
|
|
@@ -2263,6 +2274,31 @@ function graphHealth(graph, memories, at = Date.now()) {
|
|
|
2263
2274
|
}
|
|
2264
2275
|
return { unlinked, possibleDuplicates, contradictions };
|
|
2265
2276
|
}
|
|
2277
|
+
function renameEntityInMeta(meta, from, into) {
|
|
2278
|
+
const fromKey = entityKey(from);
|
|
2279
|
+
const names = [...meta.about ?? [], ...(meta.relations ?? []).flatMap((r) => [r.s, r.o])];
|
|
2280
|
+
if (!names.some((n) => entityKey(n) === fromKey))
|
|
2281
|
+
return null;
|
|
2282
|
+
const swap = (n) => entityKey(n) === fromKey ? into.trim() : n;
|
|
2283
|
+
const next = { ...meta };
|
|
2284
|
+
if (meta.about)
|
|
2285
|
+
next.about = uniqueNames(meta.about.map(swap));
|
|
2286
|
+
if (meta.relations) {
|
|
2287
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
2288
|
+
for (const r of meta.relations.map((r2) => ({ ...r2, s: swap(r2.s), o: swap(r2.o) }))) {
|
|
2289
|
+
byKey.set(`${entityKey(r.s)}|${normalizePredicate(r.p)}|${entityKey(r.o)}`, r);
|
|
2290
|
+
}
|
|
2291
|
+
next.relations = [...byKey.values()];
|
|
2292
|
+
}
|
|
2293
|
+
return next;
|
|
2294
|
+
}
|
|
2295
|
+
function uniqueNames(names) {
|
|
2296
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
2297
|
+
for (const n of names)
|
|
2298
|
+
if (!byKey.has(entityKey(n)))
|
|
2299
|
+
byKey.set(entityKey(n), n);
|
|
2300
|
+
return [...byKey.values()];
|
|
2301
|
+
}
|
|
2266
2302
|
|
|
2267
2303
|
// ../../packages/mcp-core/dist/memory-secrets.js
|
|
2268
2304
|
var PATTERNS = [
|
|
@@ -2716,19 +2752,11 @@ var MemoryStore = class {
|
|
|
2716
2752
|
await this.sync();
|
|
2717
2753
|
const key = this.requireSession();
|
|
2718
2754
|
const startEpoch = this.epoch;
|
|
2719
|
-
const fromKey = entityKey(from);
|
|
2720
|
-
const swap = (n) => entityKey(n) === fromKey ? into.trim() : n;
|
|
2721
2755
|
let changed = 0;
|
|
2722
2756
|
for (const memory of [...this.cache.values()]) {
|
|
2723
|
-
const
|
|
2724
|
-
if (!
|
|
2757
|
+
const meta = renameEntityInMeta(memory.meta, from, into);
|
|
2758
|
+
if (!meta)
|
|
2725
2759
|
continue;
|
|
2726
|
-
const meta = { ...memory.meta };
|
|
2727
|
-
if (memory.meta.about)
|
|
2728
|
-
meta.about = unionNames([], memory.meta.about.map(swap));
|
|
2729
|
-
if (memory.meta.relations) {
|
|
2730
|
-
meta.relations = unionRelations([], memory.meta.relations.map((r) => ({ ...r, s: swap(r.s), o: swap(r.o) })));
|
|
2731
|
-
}
|
|
2732
2760
|
const row = await this.writeNote(key, memory.id, memory.label, memory.text, meta);
|
|
2733
2761
|
if (this.epoch === startEpoch) {
|
|
2734
2762
|
this.cache.set(memory.id, { ...memory, meta, updatedAt: row.updatedAt });
|
|
@@ -2763,6 +2791,11 @@ var MemoryStore = class {
|
|
|
2763
2791
|
const memories = this.list(namespace);
|
|
2764
2792
|
return graphHealth(this.graph(namespace), memories);
|
|
2765
2793
|
}
|
|
2794
|
+
/** The namespace of a memory this session has seen, without a sync;
|
|
2795
|
+
* undefined for a global memory or one not cached yet. */
|
|
2796
|
+
namespaceOfCached(id) {
|
|
2797
|
+
return this.cache.get(id)?.namespace;
|
|
2798
|
+
}
|
|
2766
2799
|
/** Requires a prior sync() in the same unlock to have populated the cache. */
|
|
2767
2800
|
list(namespace) {
|
|
2768
2801
|
this.requireSession();
|
|
@@ -2983,6 +3016,29 @@ var MEMORIES_DISABLED_MESSAGE = 'AI memories are switched off for this account.
|
|
|
2983
3016
|
// ../../packages/mcp-core/dist/tools.js
|
|
2984
3017
|
import { z } from "zod";
|
|
2985
3018
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3019
|
+
|
|
3020
|
+
// ../../packages/mcp-core/dist/memory-pause.js
|
|
3021
|
+
async function capturePausedReason(api, namespace) {
|
|
3022
|
+
try {
|
|
3023
|
+
const me = await api.me();
|
|
3024
|
+
if (me.memoriesPaused === true)
|
|
3025
|
+
return MEMORIES_PAUSED_MESSAGE;
|
|
3026
|
+
if (namespace === void 0)
|
|
3027
|
+
return null;
|
|
3028
|
+
const { folders } = await api.listFolders();
|
|
3029
|
+
const root = folders.find((f) => f.kind === "memories");
|
|
3030
|
+
const paused = folders.some((f) => root !== void 0 && f.parentId === root.id && f.name === namespace && f.memoryPaused === true);
|
|
3031
|
+
return paused ? projectPausedMessage(namespace) : null;
|
|
3032
|
+
} catch {
|
|
3033
|
+
return "Not saved: could not confirm that saving memories is allowed. Try again shortly.";
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
var MEMORIES_PAUSED_MESSAGE = "Not saved: saving memories is paused in the Virlow app. Recall still works; do not retry or ask the user to save this another way.";
|
|
3037
|
+
function projectPausedMessage(namespace) {
|
|
3038
|
+
return `Not saved: saving memories is paused for the project "${namespace}" in the Virlow app. Recall still works; do not retry or ask the user to save this another way.`;
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3041
|
+
// ../../packages/mcp-core/dist/tools.js
|
|
2986
3042
|
function textResult(text) {
|
|
2987
3043
|
return { content: [{ type: "text", text }] };
|
|
2988
3044
|
}
|
|
@@ -3113,6 +3169,13 @@ function buildServer(opts) {
|
|
|
3113
3169
|
throw new Error(MEMORIES_DISABLED_MESSAGE);
|
|
3114
3170
|
}
|
|
3115
3171
|
};
|
|
3172
|
+
const requireCapture = async (namespace) => {
|
|
3173
|
+
if (!vault.isUnlocked())
|
|
3174
|
+
return;
|
|
3175
|
+
const reason = await capturePausedReason(api, namespace);
|
|
3176
|
+
if (reason)
|
|
3177
|
+
throw new Error(reason);
|
|
3178
|
+
};
|
|
3116
3179
|
const shownMemories = /* @__PURE__ */ new Set();
|
|
3117
3180
|
const relatedMemories = async (text) => {
|
|
3118
3181
|
if (opts.memoriesEnabled === false || !vault.isUnlocked())
|
|
@@ -3159,6 +3222,7 @@ function buildServer(opts) {
|
|
|
3159
3222
|
if (args.valid_from !== void 0)
|
|
3160
3223
|
meta.validFrom = args.valid_from;
|
|
3161
3224
|
await requireMemories();
|
|
3225
|
+
await requireCapture(args.namespace);
|
|
3162
3226
|
const result = await memoryStore.add(args.label, args.text, args.namespace, meta);
|
|
3163
3227
|
let message = result.action === "updated" ? `updated existing memory ${result.id}` : `stored ${result.id}`;
|
|
3164
3228
|
if (result.closed.length > 0) {
|
|
@@ -3199,12 +3263,18 @@ ${formatSyncStats(stats)}`);
|
|
|
3199
3263
|
description: "List cached memories, optionally filtered by namespace. Syncs with the server first so the list reflects the latest state across devices. Requires the vault to be unlocked.",
|
|
3200
3264
|
inputSchema: {
|
|
3201
3265
|
namespace: z.string().max(100).optional(),
|
|
3202
|
-
limit: z.number().int().min(1).max(50).optional()
|
|
3266
|
+
limit: z.number().int().min(1).max(50).optional(),
|
|
3267
|
+
unlinked_only: z.boolean().optional().describe("Only memories linked to no entity, for organising them with link_memory.")
|
|
3203
3268
|
}
|
|
3204
3269
|
}, wrap(async (args) => {
|
|
3205
3270
|
await requireMemories();
|
|
3206
3271
|
const stats = await memoryStore.sync();
|
|
3207
|
-
|
|
3272
|
+
let all = memoryStore.list(args.namespace);
|
|
3273
|
+
if (args.unlinked_only) {
|
|
3274
|
+
const unlinked = new Set((await memoryStore.health(args.namespace)).unlinked);
|
|
3275
|
+
all = all.filter((m) => unlinked.has(m.id));
|
|
3276
|
+
}
|
|
3277
|
+
const items = all.slice(0, args.limit ?? 50);
|
|
3208
3278
|
const lines = items.map((m, i) => `${i + 1}. [${m.id}] ${m.label} \u2014 ${m.text}${m.namespace ? ` (namespace: ${m.namespace})` : ""}${formatMeta(m.meta)}`);
|
|
3209
3279
|
const body = lines.length > 0 ? lines.join("\n") : "(no memories found)";
|
|
3210
3280
|
return textResult(`${body}
|
|
@@ -3241,6 +3311,7 @@ ${formatSyncStats(stats)}`);
|
|
|
3241
3311
|
}
|
|
3242
3312
|
}, wrap(async (args) => {
|
|
3243
3313
|
await requireMemories();
|
|
3314
|
+
await requireCapture(memoryStore.namespaceOfCached(args.id));
|
|
3244
3315
|
const closed = await memoryStore.link(args.id, {
|
|
3245
3316
|
...args.entities && { about: args.entities },
|
|
3246
3317
|
...args.relations && { relations: args.relations }
|
|
@@ -3256,6 +3327,7 @@ ${formatSyncStats(stats)}`);
|
|
|
3256
3327
|
}
|
|
3257
3328
|
}, wrap(async (args) => {
|
|
3258
3329
|
await requireMemories();
|
|
3330
|
+
await requireCapture();
|
|
3259
3331
|
const changed = await memoryStore.mergeEntities(args.from, args.into);
|
|
3260
3332
|
return textResult(`merged "${args.from}" into "${args.into}" in ${changed} memor${changed === 1 ? "y" : "ies"}`);
|
|
3261
3333
|
}));
|
|
@@ -3269,6 +3341,9 @@ ${formatSyncStats(stats)}`);
|
|
|
3269
3341
|
await requireMemories();
|
|
3270
3342
|
const { entities, facts } = await memoryStore.context(args.namespace, args.limit ?? 12);
|
|
3271
3343
|
const parts = [];
|
|
3344
|
+
const paused = await capturePausedReason(api, args.namespace);
|
|
3345
|
+
if (paused)
|
|
3346
|
+
parts.push("Saving memories is paused (recall only). Do not call add_memory this session.");
|
|
3272
3347
|
if (entities.length) {
|
|
3273
3348
|
parts.push("Key entities: " + entities.map((e) => `${e.name} (${e.facts})`).join(", "));
|
|
3274
3349
|
}
|
|
@@ -3297,17 +3372,23 @@ ${formatSyncStats(stats)}`);
|
|
|
3297
3372
|
label: z.string().min(1).max(80).optional(),
|
|
3298
3373
|
text: z.string().min(1).max(4e3).optional(),
|
|
3299
3374
|
tags: z.array(z.string().max(50)).max(20).optional(),
|
|
3300
|
-
confidence: z.string().max(20).optional()
|
|
3375
|
+
confidence: z.string().max(20).optional(),
|
|
3376
|
+
valid_from: z.string().max(40).optional().describe("When the fact became true (YYYY-MM-DD)."),
|
|
3377
|
+
valid_to: z.string().max(40).optional().describe("When the fact stopped being true (YYYY-MM-DD). It is kept as history, not deleted.")
|
|
3301
3378
|
}
|
|
3302
3379
|
}, wrap(async (args) => {
|
|
3303
|
-
if (args.label === void 0 && args.text === void 0 && args.tags === void 0 && args.confidence === void 0) {
|
|
3304
|
-
throw new Error("update_memory needs at least one of label, text, tags, or
|
|
3380
|
+
if (args.label === void 0 && args.text === void 0 && args.tags === void 0 && args.confidence === void 0 && args.valid_from === void 0 && args.valid_to === void 0) {
|
|
3381
|
+
throw new Error("update_memory needs at least one of label, text, tags, confidence, valid_from or valid_to");
|
|
3305
3382
|
}
|
|
3306
3383
|
const meta = {};
|
|
3307
3384
|
if (args.tags !== void 0)
|
|
3308
3385
|
meta.tags = args.tags;
|
|
3309
3386
|
if (args.confidence !== void 0)
|
|
3310
3387
|
meta.confidence = args.confidence;
|
|
3388
|
+
if (args.valid_from !== void 0)
|
|
3389
|
+
meta.validFrom = args.valid_from;
|
|
3390
|
+
if (args.valid_to !== void 0)
|
|
3391
|
+
meta.validTo = args.valid_to;
|
|
3311
3392
|
const changes = { meta };
|
|
3312
3393
|
if (args.label !== void 0)
|
|
3313
3394
|
changes.label = args.label;
|
|
@@ -3643,7 +3724,7 @@ var SECURITY_HEADERS = Object.freeze({
|
|
|
3643
3724
|
"content-security-policy": "default-src 'none'; style-src 'unsafe-inline'; form-action 'self'; frame-ancestors 'none'; base-uri 'none'",
|
|
3644
3725
|
"x-frame-options": "DENY",
|
|
3645
3726
|
"x-content-type-options": "nosniff",
|
|
3646
|
-
"referrer-policy": "
|
|
3727
|
+
"referrer-policy": "same-origin",
|
|
3647
3728
|
"cache-control": "no-store"
|
|
3648
3729
|
});
|
|
3649
3730
|
|
|
@@ -4005,7 +4086,7 @@ function startUnlockServer(api, vault, opts = {}) {
|
|
|
4005
4086
|
}
|
|
4006
4087
|
|
|
4007
4088
|
// src/version.ts
|
|
4008
|
-
var VERSION = true ? "3.
|
|
4089
|
+
var VERSION = true ? "3.70.0" : "dev";
|
|
4009
4090
|
|
|
4010
4091
|
// src/server.ts
|
|
4011
4092
|
function defaultEmbeddingCacheDir() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "virlow-mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.70.0",
|
|
4
4
|
"description": "Local MCP server for Virlow Secure Notes: end-to-end-encrypted AI memories and notes for Cursor, Claude, Codex, and any MCP client.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
"esbuild": "^0.25.0",
|
|
35
35
|
"typescript": "^5.8.0",
|
|
36
36
|
"vitest": "^3.0.0",
|
|
37
|
-
"@batalabs/virlow-
|
|
38
|
-
"@batalabs/virlow-
|
|
39
|
-
"@batalabs/virlow-
|
|
37
|
+
"@batalabs/virlow-memory": "3.70.0",
|
|
38
|
+
"@batalabs/virlow-mcp-core": "3.70.0",
|
|
39
|
+
"@batalabs/virlow-crypto": "3.70.0"
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
42
|
"build": "node build.mjs",
|