virlow-mcp 3.69.1 → 3.70.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/dist/cli.js +117 -23
- 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();
|
|
@@ -2979,10 +3012,48 @@ async function memoriesAllowed(api) {
|
|
|
2979
3012
|
}
|
|
2980
3013
|
}
|
|
2981
3014
|
var MEMORIES_DISABLED_MESSAGE = 'AI memories are switched off for this account. Turn on "AI memories" in Settings \u2192 Security in the Virlow app to use this tool.';
|
|
3015
|
+
async function requireMemoriesSetting(api) {
|
|
3016
|
+
let me;
|
|
3017
|
+
try {
|
|
3018
|
+
me = await api.me();
|
|
3019
|
+
} catch (err) {
|
|
3020
|
+
throw new Error(memoriesUnreadableMessage(err));
|
|
3021
|
+
}
|
|
3022
|
+
if (me.memoriesEnabled !== true)
|
|
3023
|
+
throw new Error(MEMORIES_DISABLED_MESSAGE);
|
|
3024
|
+
}
|
|
3025
|
+
function memoriesUnreadableMessage(err) {
|
|
3026
|
+
const status = err instanceof ApiError ? err.status : void 0;
|
|
3027
|
+
const reason = status === 429 ? "Virlow is rate limiting requests right now" : `Virlow could not be reached (${err instanceof Error ? err.message : String(err)})`;
|
|
3028
|
+
return `Could not check whether AI memories are on: ${reason}. Nothing was changed. Wait a minute and try again; this is not the AI memories switch.`;
|
|
3029
|
+
}
|
|
2982
3030
|
|
|
2983
3031
|
// ../../packages/mcp-core/dist/tools.js
|
|
2984
3032
|
import { z } from "zod";
|
|
2985
3033
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3034
|
+
|
|
3035
|
+
// ../../packages/mcp-core/dist/memory-pause.js
|
|
3036
|
+
async function capturePausedReason(api, namespace) {
|
|
3037
|
+
try {
|
|
3038
|
+
const me = await api.me();
|
|
3039
|
+
if (me.memoriesPaused === true)
|
|
3040
|
+
return MEMORIES_PAUSED_MESSAGE;
|
|
3041
|
+
if (namespace === void 0)
|
|
3042
|
+
return null;
|
|
3043
|
+
const { folders } = await api.listFolders();
|
|
3044
|
+
const root = folders.find((f) => f.kind === "memories");
|
|
3045
|
+
const paused = folders.some((f) => root !== void 0 && f.parentId === root.id && f.name === namespace && f.memoryPaused === true);
|
|
3046
|
+
return paused ? projectPausedMessage(namespace) : null;
|
|
3047
|
+
} catch {
|
|
3048
|
+
return "Not saved: could not confirm that saving memories is allowed. Try again shortly.";
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
3051
|
+
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.";
|
|
3052
|
+
function projectPausedMessage(namespace) {
|
|
3053
|
+
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.`;
|
|
3054
|
+
}
|
|
3055
|
+
|
|
3056
|
+
// ../../packages/mcp-core/dist/tools.js
|
|
2986
3057
|
function textResult(text) {
|
|
2987
3058
|
return { content: [{ type: "text", text }] };
|
|
2988
3059
|
}
|
|
@@ -3109,9 +3180,14 @@ function buildServer(opts) {
|
|
|
3109
3180
|
const requireMemories = async () => {
|
|
3110
3181
|
if (!vault.isUnlocked())
|
|
3111
3182
|
return;
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3183
|
+
await requireMemoriesSetting(api);
|
|
3184
|
+
};
|
|
3185
|
+
const requireCapture = async (namespace) => {
|
|
3186
|
+
if (!vault.isUnlocked())
|
|
3187
|
+
return;
|
|
3188
|
+
const reason = await capturePausedReason(api, namespace);
|
|
3189
|
+
if (reason)
|
|
3190
|
+
throw new Error(reason);
|
|
3115
3191
|
};
|
|
3116
3192
|
const shownMemories = /* @__PURE__ */ new Set();
|
|
3117
3193
|
const relatedMemories = async (text) => {
|
|
@@ -3159,6 +3235,7 @@ function buildServer(opts) {
|
|
|
3159
3235
|
if (args.valid_from !== void 0)
|
|
3160
3236
|
meta.validFrom = args.valid_from;
|
|
3161
3237
|
await requireMemories();
|
|
3238
|
+
await requireCapture(args.namespace);
|
|
3162
3239
|
const result = await memoryStore.add(args.label, args.text, args.namespace, meta);
|
|
3163
3240
|
let message = result.action === "updated" ? `updated existing memory ${result.id}` : `stored ${result.id}`;
|
|
3164
3241
|
if (result.closed.length > 0) {
|
|
@@ -3199,12 +3276,18 @@ ${formatSyncStats(stats)}`);
|
|
|
3199
3276
|
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
3277
|
inputSchema: {
|
|
3201
3278
|
namespace: z.string().max(100).optional(),
|
|
3202
|
-
limit: z.number().int().min(1).max(50).optional()
|
|
3279
|
+
limit: z.number().int().min(1).max(50).optional(),
|
|
3280
|
+
unlinked_only: z.boolean().optional().describe("Only memories linked to no entity, for organising them with link_memory.")
|
|
3203
3281
|
}
|
|
3204
3282
|
}, wrap(async (args) => {
|
|
3205
3283
|
await requireMemories();
|
|
3206
3284
|
const stats = await memoryStore.sync();
|
|
3207
|
-
|
|
3285
|
+
let all = memoryStore.list(args.namespace);
|
|
3286
|
+
if (args.unlinked_only) {
|
|
3287
|
+
const unlinked = new Set((await memoryStore.health(args.namespace)).unlinked);
|
|
3288
|
+
all = all.filter((m) => unlinked.has(m.id));
|
|
3289
|
+
}
|
|
3290
|
+
const items = all.slice(0, args.limit ?? 50);
|
|
3208
3291
|
const lines = items.map((m, i) => `${i + 1}. [${m.id}] ${m.label} \u2014 ${m.text}${m.namespace ? ` (namespace: ${m.namespace})` : ""}${formatMeta(m.meta)}`);
|
|
3209
3292
|
const body = lines.length > 0 ? lines.join("\n") : "(no memories found)";
|
|
3210
3293
|
return textResult(`${body}
|
|
@@ -3241,6 +3324,7 @@ ${formatSyncStats(stats)}`);
|
|
|
3241
3324
|
}
|
|
3242
3325
|
}, wrap(async (args) => {
|
|
3243
3326
|
await requireMemories();
|
|
3327
|
+
await requireCapture(memoryStore.namespaceOfCached(args.id));
|
|
3244
3328
|
const closed = await memoryStore.link(args.id, {
|
|
3245
3329
|
...args.entities && { about: args.entities },
|
|
3246
3330
|
...args.relations && { relations: args.relations }
|
|
@@ -3256,6 +3340,7 @@ ${formatSyncStats(stats)}`);
|
|
|
3256
3340
|
}
|
|
3257
3341
|
}, wrap(async (args) => {
|
|
3258
3342
|
await requireMemories();
|
|
3343
|
+
await requireCapture();
|
|
3259
3344
|
const changed = await memoryStore.mergeEntities(args.from, args.into);
|
|
3260
3345
|
return textResult(`merged "${args.from}" into "${args.into}" in ${changed} memor${changed === 1 ? "y" : "ies"}`);
|
|
3261
3346
|
}));
|
|
@@ -3269,6 +3354,9 @@ ${formatSyncStats(stats)}`);
|
|
|
3269
3354
|
await requireMemories();
|
|
3270
3355
|
const { entities, facts } = await memoryStore.context(args.namespace, args.limit ?? 12);
|
|
3271
3356
|
const parts = [];
|
|
3357
|
+
const paused = await capturePausedReason(api, args.namespace);
|
|
3358
|
+
if (paused)
|
|
3359
|
+
parts.push("Saving memories is paused (recall only). Do not call add_memory this session.");
|
|
3272
3360
|
if (entities.length) {
|
|
3273
3361
|
parts.push("Key entities: " + entities.map((e) => `${e.name} (${e.facts})`).join(", "));
|
|
3274
3362
|
}
|
|
@@ -3297,17 +3385,23 @@ ${formatSyncStats(stats)}`);
|
|
|
3297
3385
|
label: z.string().min(1).max(80).optional(),
|
|
3298
3386
|
text: z.string().min(1).max(4e3).optional(),
|
|
3299
3387
|
tags: z.array(z.string().max(50)).max(20).optional(),
|
|
3300
|
-
confidence: z.string().max(20).optional()
|
|
3388
|
+
confidence: z.string().max(20).optional(),
|
|
3389
|
+
valid_from: z.string().max(40).optional().describe("When the fact became true (YYYY-MM-DD)."),
|
|
3390
|
+
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
3391
|
}
|
|
3302
3392
|
}, 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
|
|
3393
|
+
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) {
|
|
3394
|
+
throw new Error("update_memory needs at least one of label, text, tags, confidence, valid_from or valid_to");
|
|
3305
3395
|
}
|
|
3306
3396
|
const meta = {};
|
|
3307
3397
|
if (args.tags !== void 0)
|
|
3308
3398
|
meta.tags = args.tags;
|
|
3309
3399
|
if (args.confidence !== void 0)
|
|
3310
3400
|
meta.confidence = args.confidence;
|
|
3401
|
+
if (args.valid_from !== void 0)
|
|
3402
|
+
meta.validFrom = args.valid_from;
|
|
3403
|
+
if (args.valid_to !== void 0)
|
|
3404
|
+
meta.validTo = args.valid_to;
|
|
3311
3405
|
const changes = { meta };
|
|
3312
3406
|
if (args.label !== void 0)
|
|
3313
3407
|
changes.label = args.label;
|
|
@@ -4005,7 +4099,7 @@ function startUnlockServer(api, vault, opts = {}) {
|
|
|
4005
4099
|
}
|
|
4006
4100
|
|
|
4007
4101
|
// src/version.ts
|
|
4008
|
-
var VERSION = true ? "3.
|
|
4102
|
+
var VERSION = true ? "3.70.1" : "dev";
|
|
4009
4103
|
|
|
4010
4104
|
// src/server.ts
|
|
4011
4105
|
function defaultEmbeddingCacheDir() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "virlow-mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.70.1",
|
|
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-crypto": "3.
|
|
37
|
+
"@batalabs/virlow-memory": "3.70.1",
|
|
38
|
+
"@batalabs/virlow-mcp-core": "3.70.1",
|
|
39
|
+
"@batalabs/virlow-crypto": "3.70.1"
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
42
|
"build": "node build.mjs",
|