virlow-mcp 3.81.0 → 3.82.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 +49 -15
- package/dist/cli.js +88 -14
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -7,8 +7,8 @@ entire time.
|
|
|
7
7
|
|
|
8
8
|
## What it is
|
|
9
9
|
|
|
10
|
-
virlow-mcp runs on your machine as a local process (`npx -y virlow-mcp`, launched by your
|
|
11
|
-
AI client over stdio). It exposes
|
|
10
|
+
virlow-mcp runs on your machine as a local process (`npx -y virlow-mcp@latest`, launched by your
|
|
11
|
+
AI client over stdio). It exposes tools for four things:
|
|
12
12
|
|
|
13
13
|
- **Memories** — short, durable facts ("the user prefers TypeScript", "this repo's CI
|
|
14
14
|
runs on self-hosted runners") that any AI tool can add, search semantically, and recall
|
|
@@ -30,6 +30,16 @@ AI client over stdio). It exposes 24 tools for three things:
|
|
|
30
30
|
schema and values in the note's encrypted attrs, so the server sees neither. Properties
|
|
31
31
|
ship in the Virlow app as of its epic 3, so what is written here is what the user sees in
|
|
32
32
|
the note, the table, the board and the calendar.
|
|
33
|
+
- **Attachments**: files on a note. `list_attachments`, `add_attachment`,
|
|
34
|
+
`read_attachment`, `rename_attachment`, `move_attachment`, `delete_attachment` and
|
|
35
|
+
`restore_attachment`. Files are encrypted in this process before upload, the same way the
|
|
36
|
+
app does it. `add_attachment` takes `text` or `content_base64` (up to 2 MB), or `path`, an
|
|
37
|
+
absolute path to a file on this machine (up to 35 MB). `read_attachment` returns text and
|
|
38
|
+
small files inline, and with `save_to` writes the decrypted file to disk instead; it never
|
|
39
|
+
replaces an existing file unless `overwrite` is true. `delete_attachment` moves a file to the
|
|
40
|
+
note's Recently deleted, where `restore_attachment` can bring it back for 30 days. `path` and
|
|
41
|
+
`save_to` exist only when this server runs on your machine over stdio (or `--http` on
|
|
42
|
+
loopback); the hosted connector and a non-loopback `--http` take inline content only.
|
|
33
43
|
|
|
34
44
|
Everything is **end-to-end encrypted, zero-knowledge**: the encryption key is derived
|
|
35
45
|
from your Virlow master password inside this local process and never leaves it. Every
|
|
@@ -44,7 +54,7 @@ through a tool call, and never seen by the AI model.
|
|
|
44
54
|
### This is the local server, and that distinction is the whole security model
|
|
45
55
|
|
|
46
56
|
Every claim above is about **this package**, running on **your machine**. Virlow also
|
|
47
|
-
offers a **hosted connector**: the same
|
|
57
|
+
offers a **hosted connector**: the same tools, served from Virlow's own servers, so a
|
|
48
58
|
client can connect by URL with nothing installed. It is a genuinely different trust model,
|
|
49
59
|
and the sentence "the key never leaves it" is the part that stops being true:
|
|
50
60
|
|
|
@@ -66,15 +76,25 @@ guarantee.
|
|
|
66
76
|
|
|
67
77
|
## Install / configure
|
|
68
78
|
|
|
69
|
-
virlow-mcp needs no separate install
|
|
79
|
+
virlow-mcp needs no separate install: `npx -y virlow-mcp@latest` fetches and runs it directly.
|
|
70
80
|
Add it to your client's MCP config:
|
|
71
81
|
|
|
72
82
|
### Claude Code
|
|
73
83
|
|
|
74
84
|
```
|
|
75
|
-
claude mcp add virlow -- npx -y virlow-mcp
|
|
85
|
+
claude mcp add virlow -- npx -y virlow-mcp@latest
|
|
76
86
|
```
|
|
77
87
|
|
|
88
|
+
On Windows, `npx` is a batch script, so wrap it in `cmd /c`:
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
claude mcp add virlow -- cmd /c npx -y virlow-mcp@latest
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Run that from PowerShell or cmd. Git Bash rewrites `/c` into a path; there, edit
|
|
95
|
+
`~/.claude.json` so the server's `"command"` is `"cmd"` and its `"args"` are
|
|
96
|
+
`["/c", "npx", "-y", "virlow-mcp@latest"]`.
|
|
97
|
+
|
|
78
98
|
### Claude Desktop
|
|
79
99
|
|
|
80
100
|
Add to your `claude_desktop_config.json`
|
|
@@ -86,7 +106,7 @@ Windows: `%APPDATA%\Claude\claude_desktop_config.json`):
|
|
|
86
106
|
"mcpServers": {
|
|
87
107
|
"virlow": {
|
|
88
108
|
"command": "npx",
|
|
89
|
-
"args": ["-y", "virlow-mcp"]
|
|
109
|
+
"args": ["-y", "virlow-mcp@latest"]
|
|
90
110
|
}
|
|
91
111
|
}
|
|
92
112
|
}
|
|
@@ -101,7 +121,7 @@ Add to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (project-local):
|
|
|
101
121
|
"mcpServers": {
|
|
102
122
|
"virlow": {
|
|
103
123
|
"command": "npx",
|
|
104
|
-
"args": ["-y", "virlow-mcp"]
|
|
124
|
+
"args": ["-y", "virlow-mcp@latest"]
|
|
105
125
|
}
|
|
106
126
|
}
|
|
107
127
|
}
|
|
@@ -114,7 +134,7 @@ Codex's MCP config is TOML, not JSON. Add to `~/.codex/config.toml`:
|
|
|
114
134
|
```toml
|
|
115
135
|
[mcp_servers.virlow]
|
|
116
136
|
command = "npx"
|
|
117
|
-
args = ["-y", "virlow-mcp"]
|
|
137
|
+
args = ["-y", "virlow-mcp@latest"]
|
|
118
138
|
```
|
|
119
139
|
|
|
120
140
|
### Self-hosting Virlow
|
|
@@ -128,7 +148,7 @@ Claude Desktop / Cursor:
|
|
|
128
148
|
"mcpServers": {
|
|
129
149
|
"virlow": {
|
|
130
150
|
"command": "npx",
|
|
131
|
-
"args": ["-y", "virlow-mcp"],
|
|
151
|
+
"args": ["-y", "virlow-mcp@latest"],
|
|
132
152
|
"env": { "VIRLOW_API_URL": "https://api.your-domain.com" }
|
|
133
153
|
}
|
|
134
154
|
}
|
|
@@ -147,7 +167,7 @@ only spawn a command use this package as a pipe:
|
|
|
147
167
|
"mcpServers": {
|
|
148
168
|
"virlow": {
|
|
149
169
|
"command": "npx",
|
|
150
|
-
"args": ["-y", "virlow-mcp", "--connect", "http://127.0.0.1:47821/mcp",
|
|
170
|
+
"args": ["-y", "virlow-mcp@latest", "--connect", "http://127.0.0.1:47821/mcp",
|
|
151
171
|
"--token-file", "<path the app shows you>"]
|
|
152
172
|
}
|
|
153
173
|
}
|
|
@@ -156,6 +176,20 @@ only spawn a command use this package as a pipe:
|
|
|
156
176
|
|
|
157
177
|
In this mode nothing is decrypted in this process. It forwards to the app and back.
|
|
158
178
|
|
|
179
|
+
### Staying up to date
|
|
180
|
+
|
|
181
|
+
With `virlow-mcp@latest` in the command, your client gets the newest release each time it
|
|
182
|
+
starts the server, so restarting the client (or opening a new session) is the whole update.
|
|
183
|
+
Without `@latest`, `npx` can keep running a copy it downloaded earlier; add `@latest` to an
|
|
184
|
+
existing config to fix that. Pin an exact version (`virlow-mcp@3.81.0`) only if you want
|
|
185
|
+
updates to wait for you. The desktop app updates itself and its built-in server, and the
|
|
186
|
+
hosted connector is updated on Virlow's side.
|
|
187
|
+
|
|
188
|
+
The server checks npm once at startup, and the `status` tool and the unlock page say when a
|
|
189
|
+
newer version exists. The request tells npm only that someone at your IP checked
|
|
190
|
+
virlow-mcp's version; it carries no account data. Turn it off with
|
|
191
|
+
`VIRLOW_MCP_NO_UPDATE_CHECK=1`.
|
|
192
|
+
|
|
159
193
|
## First use
|
|
160
194
|
|
|
161
195
|
1. Ask your AI client to run the **`unlock`** tool. This opens a browser window pointed
|
|
@@ -206,13 +240,13 @@ just give you two versions to keep in sync.
|
|
|
206
240
|
## Reaching your vault from a session that is not on this machine
|
|
207
241
|
|
|
208
242
|
By default `virlow-mcp` speaks stdio, so your AI client spawns it as a child
|
|
209
|
-
process and everything stays on one machine. `--http` serves the same
|
|
243
|
+
process and everything stays on one machine. `--http` serves the same tools
|
|
210
244
|
over HTTP instead, so a client that cannot spawn a local process — a cloud
|
|
211
245
|
coding session, another laptop — can reach this vault:
|
|
212
246
|
|
|
213
247
|
```bash
|
|
214
|
-
npx -y virlow-mcp --http # loopback, random port
|
|
215
|
-
npx -y virlow-mcp --http --port 8787 # pick the port
|
|
248
|
+
npx -y virlow-mcp@latest --http # loopback, random port
|
|
249
|
+
npx -y virlow-mcp@latest --http --port 8787 # pick the port
|
|
216
250
|
```
|
|
217
251
|
|
|
218
252
|
It prints the URL and the path to a bearer token, generated on first run and
|
|
@@ -295,12 +329,12 @@ account cannot be reached, the tools refuse rather than assume consent.
|
|
|
295
329
|
unregistered, so they do not appear in `tools/list` at all:
|
|
296
330
|
|
|
297
331
|
```bash
|
|
298
|
-
npx -y virlow-mcp --no-memories
|
|
332
|
+
npx -y virlow-mcp@latest --no-memories
|
|
299
333
|
```
|
|
300
334
|
|
|
301
335
|
Use it when you want a connector that structurally cannot touch memories,
|
|
302
336
|
regardless of the account setting. Notes tools, `unlock`, `lock`, and `status`
|
|
303
|
-
are unaffected either way
|
|
337
|
+
are unaffected either way.
|
|
304
338
|
|
|
305
339
|
## Suggested agent rules
|
|
306
340
|
|
package/dist/cli.js
CHANGED
|
@@ -3859,7 +3859,8 @@ function buildServer(opts) {
|
|
|
3859
3859
|
server.registerTool("status", {
|
|
3860
3860
|
description: "Report whether the vault is currently locked or unlocked, and which account it's unlocked as. Call this after unlock to confirm the user finished the browser form, and before any memory/notes tool if you are unsure of the current lock state."
|
|
3861
3861
|
}, wrap(async () => {
|
|
3862
|
-
|
|
3862
|
+
const notice = opts.updateNotice?.() ?? null;
|
|
3863
|
+
return textResult(JSON.stringify(notice === null ? vault.status() : { ...vault.status(), update: notice }));
|
|
3863
3864
|
}));
|
|
3864
3865
|
const requireMemories = async () => {
|
|
3865
3866
|
if (!vault.isUnlocked())
|
|
@@ -4518,8 +4519,9 @@ async function performUnlock(api, vault, creds) {
|
|
|
4518
4519
|
vault.touch();
|
|
4519
4520
|
};
|
|
4520
4521
|
}
|
|
4521
|
-
function renderForm(nonce, error) {
|
|
4522
|
+
function renderForm(nonce, error, updateNotice) {
|
|
4522
4523
|
const errorHtml = error ? `<p class="error">${escapeHtml(error)}</p>` : "";
|
|
4524
|
+
const updateHtml = updateNotice ? `<p class="update">${escapeHtml(updateNotice)}</p>` : "";
|
|
4523
4525
|
return `<!doctype html>
|
|
4524
4526
|
<html lang="en">
|
|
4525
4527
|
<head>
|
|
@@ -4528,6 +4530,7 @@ function renderForm(nonce, error) {
|
|
|
4528
4530
|
<style>
|
|
4529
4531
|
body { font-family: system-ui, sans-serif; max-width: 420px; margin: 48px auto; padding: 0 16px; color: #1a1a1a; }
|
|
4530
4532
|
h1 { font-size: 1.4rem; }
|
|
4533
|
+
p.update { color: #555; font-size: 0.9rem; }
|
|
4531
4534
|
label { display: block; margin-top: 12px; font-weight: 600; }
|
|
4532
4535
|
input { width: 100%; padding: 8px; margin-top: 4px; box-sizing: border-box; font-size: 1rem; }
|
|
4533
4536
|
button { margin-top: 20px; padding: 10px 16px; font-size: 1rem; cursor: pointer; }
|
|
@@ -4542,6 +4545,7 @@ function renderForm(nonce, error) {
|
|
|
4542
4545
|
Virlow API. They are never seen by any AI model or logged.
|
|
4543
4546
|
</p>
|
|
4544
4547
|
${errorHtml}
|
|
4548
|
+
${updateHtml}
|
|
4545
4549
|
<form method="POST" action="/unlock?nonce=${encodeURIComponent(nonce)}" autocomplete="off">
|
|
4546
4550
|
<label for="email">Email</label>
|
|
4547
4551
|
<input type="email" id="email" name="email" autocomplete="off" required />
|
|
@@ -4686,7 +4690,7 @@ function startUnlockServer(api, vault, opts = {}) {
|
|
|
4686
4690
|
harden(res);
|
|
4687
4691
|
res.statusCode = 200;
|
|
4688
4692
|
res.setHeader("content-type", "text/html; charset=utf-8");
|
|
4689
|
-
res.end(renderForm(nonce));
|
|
4693
|
+
res.end(renderForm(nonce, void 0, opts.updateNotice?.() ?? null));
|
|
4690
4694
|
return;
|
|
4691
4695
|
}
|
|
4692
4696
|
if (req.method === "POST") {
|
|
@@ -4710,7 +4714,7 @@ function startUnlockServer(api, vault, opts = {}) {
|
|
|
4710
4714
|
harden(res);
|
|
4711
4715
|
res.statusCode = 200;
|
|
4712
4716
|
res.setHeader("content-type", "text/html; charset=utf-8");
|
|
4713
|
-
res.end(renderForm(nonce, message));
|
|
4717
|
+
res.end(renderForm(nonce, message, opts.updateNotice?.() ?? null));
|
|
4714
4718
|
}
|
|
4715
4719
|
return;
|
|
4716
4720
|
}
|
|
@@ -4742,25 +4746,30 @@ function startUnlockServer(api, vault, opts = {}) {
|
|
|
4742
4746
|
}
|
|
4743
4747
|
|
|
4744
4748
|
// src/version.ts
|
|
4745
|
-
var VERSION = true ? "3.
|
|
4749
|
+
var VERSION = true ? "3.82.0" : "dev";
|
|
4746
4750
|
|
|
4747
4751
|
// src/server.ts
|
|
4748
4752
|
function defaultEmbeddingCacheDir() {
|
|
4749
4753
|
return path3.join(os.homedir(), ".virlow-mcp", "cache");
|
|
4750
4754
|
}
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
}
|
|
4755
|
+
function localBrowserUnlock(updateNotice) {
|
|
4756
|
+
return {
|
|
4757
|
+
description: "Open a local browser window where the USER signs in and enters their Virlow master password. This tool returns immediately once the browser is opened \u2014 it does NOT wait for the user to finish. Call the status tool afterward to confirm the vault unlocked. Never ask the user for their password or account credentials yourself, and never relay a password through this tool or any other \u2014 credentials are entered only in the browser form and never seen by any AI model.",
|
|
4758
|
+
async begin({ api, vault }) {
|
|
4759
|
+
const { url } = await startUnlockServer(api, vault, {
|
|
4760
|
+
openBrowser: true,
|
|
4761
|
+
...updateNotice ? { updateNotice } : {}
|
|
4762
|
+
});
|
|
4763
|
+
return `Browser opened at ${url}. Complete the unlock form there, then run status to confirm.`;
|
|
4764
|
+
}
|
|
4765
|
+
};
|
|
4766
|
+
}
|
|
4758
4767
|
function withLocalDefaults(opts) {
|
|
4759
4768
|
const { embeddingCacheDir, ...rest } = opts;
|
|
4760
4769
|
return {
|
|
4761
4770
|
...rest,
|
|
4762
4771
|
serverInfo: { name: "virlow-mcp", version: VERSION },
|
|
4763
|
-
unlock: localBrowserUnlock,
|
|
4772
|
+
unlock: localBrowserUnlock(opts.updateNotice),
|
|
4764
4773
|
createEmbeddingCache: (userId) => new EmbeddingCache({
|
|
4765
4774
|
dir: embeddingCacheDir ?? defaultEmbeddingCacheDir(),
|
|
4766
4775
|
userId,
|
|
@@ -4828,6 +4837,63 @@ async function runConnectBridge(opts) {
|
|
|
4828
4837
|
};
|
|
4829
4838
|
}
|
|
4830
4839
|
|
|
4840
|
+
// src/update-check.ts
|
|
4841
|
+
var REGISTRY_URL = "https://registry.npmjs.org/virlow-mcp/latest";
|
|
4842
|
+
var TIMEOUT_MS = 3e3;
|
|
4843
|
+
var OPT_OUT_ENV = "VIRLOW_MCP_NO_UPDATE_CHECK";
|
|
4844
|
+
function parseVersion(v) {
|
|
4845
|
+
const m = /^(\d+)\.(\d+)\.(\d+)$/.exec(v);
|
|
4846
|
+
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
|
4847
|
+
}
|
|
4848
|
+
function isNewer(candidate, current) {
|
|
4849
|
+
const a = parseVersion(candidate);
|
|
4850
|
+
const b = parseVersion(current);
|
|
4851
|
+
if (!a || !b) return false;
|
|
4852
|
+
for (let i = 0; i < 3; i++) {
|
|
4853
|
+
if (a[i] !== b[i]) return a[i] > b[i];
|
|
4854
|
+
}
|
|
4855
|
+
return false;
|
|
4856
|
+
}
|
|
4857
|
+
function optedOut(env) {
|
|
4858
|
+
const v = env[OPT_OUT_ENV];
|
|
4859
|
+
return v === "1" || v === "true";
|
|
4860
|
+
}
|
|
4861
|
+
function updateCheckApplies(argv, env) {
|
|
4862
|
+
if (optedOut(env)) return false;
|
|
4863
|
+
if (argv.includes("--version") || argv.includes("-v")) return false;
|
|
4864
|
+
if (argv.some((a) => a === "--connect" || a.startsWith("--connect="))) return false;
|
|
4865
|
+
return true;
|
|
4866
|
+
}
|
|
4867
|
+
function startUpdateCheck(opts) {
|
|
4868
|
+
const env = opts.env ?? process.env;
|
|
4869
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
4870
|
+
const log = opts.log ?? ((line) => process.stderr.write(`${line}
|
|
4871
|
+
`));
|
|
4872
|
+
let text = null;
|
|
4873
|
+
if (optedOut(env)) return { notice: () => null, done: Promise.resolve() };
|
|
4874
|
+
const done = (async () => {
|
|
4875
|
+
try {
|
|
4876
|
+
const res = await fetchImpl(REGISTRY_URL, {
|
|
4877
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
4878
|
+
headers: { accept: "application/json" }
|
|
4879
|
+
});
|
|
4880
|
+
if (!res.ok) return;
|
|
4881
|
+
const body = await res.json();
|
|
4882
|
+
const latest = body.version;
|
|
4883
|
+
if (typeof latest === "string" && isNewer(latest, opts.current)) {
|
|
4884
|
+
text = `virlow-mcp ${latest} is available (you are running ${opts.current}). Restart your AI client to update. If your MCP config runs plain virlow-mcp, change it to virlow-mcp@latest.`;
|
|
4885
|
+
}
|
|
4886
|
+
} catch (err) {
|
|
4887
|
+
const why = err instanceof Error ? err.message : String(err);
|
|
4888
|
+
try {
|
|
4889
|
+
log(`virlow-mcp: update check skipped: ${why.replace(/\s+/g, " ")}`);
|
|
4890
|
+
} catch {
|
|
4891
|
+
}
|
|
4892
|
+
}
|
|
4893
|
+
})();
|
|
4894
|
+
return { notice: () => text, done };
|
|
4895
|
+
}
|
|
4896
|
+
|
|
4831
4897
|
// src/cli.ts
|
|
4832
4898
|
function flagValue(name) {
|
|
4833
4899
|
const prefix = `--${name}=`;
|
|
@@ -4860,7 +4926,15 @@ async function main() {
|
|
|
4860
4926
|
const http2 = process.argv.includes("--http");
|
|
4861
4927
|
const host = flagValue("host") ?? "127.0.0.1";
|
|
4862
4928
|
const fileAccess = localFileAccess({ http: http2, host });
|
|
4863
|
-
const
|
|
4929
|
+
const updates = updateCheckApplies(process.argv, process.env) ? startUpdateCheck({ current: VERSION }) : null;
|
|
4930
|
+
const serverOpts = {
|
|
4931
|
+
apiUrl,
|
|
4932
|
+
modelCacheDir,
|
|
4933
|
+
embeddingCacheDir,
|
|
4934
|
+
memoriesEnabled,
|
|
4935
|
+
fileAccess,
|
|
4936
|
+
...updates ? { updateNotice: () => updates.notice() } : {}
|
|
4937
|
+
};
|
|
4864
4938
|
const services = createServices2(serverOpts);
|
|
4865
4939
|
const { server } = buildServer2({ ...serverOpts, services });
|
|
4866
4940
|
if (http2) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "virlow-mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.82.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-crypto": "3.
|
|
38
|
-
"@batalabs/virlow-
|
|
39
|
-
"@batalabs/virlow-
|
|
37
|
+
"@batalabs/virlow-crypto": "3.82.0",
|
|
38
|
+
"@batalabs/virlow-memory": "3.82.0",
|
|
39
|
+
"@batalabs/virlow-mcp-core": "3.82.0"
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
42
|
"build": "node build.mjs",
|