@squirrelnotes.app/mcp 2.1.0 → 2.1.1-test.341
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 +98 -5
- package/dist/stdio-handler.js +1531 -167
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -12,10 +12,12 @@ The passphrase lives in `claude_desktop_config.json` on your machine and is pass
|
|
|
12
12
|
|
|
13
13
|
| Risk | Mitigation |
|
|
14
14
|
|------|------------|
|
|
15
|
-
| Passphrase in config file | Stored on disk in a local config file; protect it with OS user permissions and full-disk encryption |
|
|
16
|
-
| Passphrase in process memory |
|
|
15
|
+
| Passphrase in config file | Stored on disk in a local config file; protect it with OS user permissions and full-disk encryption. Anyone who can read this file has your passphrase. |
|
|
16
|
+
| Passphrase and key in process memory | The process derives your data key once, on its first tool call, and holds it in memory until the process exits, even while it is idle between requests. The MCP server never writes it to a file or a log. The operating system can still put it on disk, through swap or a crash dump, and a debugger running as your user can read it, as all of these could already read the passphrase in the process's environment. |
|
|
17
17
|
| Decrypted content visible to AI | This is the intended behaviour — AI agents need to read your notes |
|
|
18
18
|
|
|
19
|
+
**Ending a running session.** Because the stdio process holds your data key for its whole life, **changing your passphrase in the app does not stop a session that is already running.** A passphrase change only re-wraps the key; it does not change it. To end a running session, **revoke or rotate the API key** (Settings → API & Data). An idle process keeps the key until its next request that reaches the API-key guard. On that request the server rejects the key and the process drops it (the reference is released, not zeroed). A request that fails before the guard (a 400 or 413 from the body parser, an API Gateway 429, or a 5xx) does not drop the key, but serves no data. The process drops the key when the server answers 401 "Invalid API key" (a missing, unknown, revoked or rotated key), or 403 "API access requires a Pro subscription" (the key is valid but the account is no longer Pro). **After a revoked or rotated key**, every later call fails until the process is restarted with a valid key. **After a Pro lapse** the key is still valid, so every call fails only while the account is not Pro: once you subscribe again, the next call derives your data key afresh and the session works again, without a restart. A 403 that a route raises for another reason, such as a shared note you do not own, leaves the key held. Quitting the MCP client also ends the process.
|
|
20
|
+
|
|
19
21
|
---
|
|
20
22
|
|
|
21
23
|
## Tools
|
|
@@ -31,7 +33,7 @@ The passphrase lives in `claude_desktop_config.json` on your machine and is pass
|
|
|
31
33
|
| `update_note` | Updates title, content, or pinnedAt on a note. |
|
|
32
34
|
| `delete_note` | Soft-deletes a note (recoverable). |
|
|
33
35
|
| `pin_note` | Pins or unpins a note (appears at top of sidebar). |
|
|
34
|
-
| `append_to_note` | Appends markdown content to an existing note.
|
|
36
|
+
| `append_to_note` | Appends markdown content to an existing note. Saves the agent a get → update, but it is **not** race-free: it reads the note and writes it back whole, and the server has no version check, so an edit made elsewhere in between is overwritten. |
|
|
35
37
|
| `set_note_tags` | Replaces all tags on a note. |
|
|
36
38
|
| `search_notes` | Fast title-only search across all notes. |
|
|
37
39
|
| `search_note_content` | Full-content search (slow — decrypts all notes). Use only when title search fails. |
|
|
@@ -55,6 +57,84 @@ The passphrase lives in `claude_desktop_config.json` on your machine and is pass
|
|
|
55
57
|
| `delete_attachment` | Permanently deletes an attachment from S3 and the database. |
|
|
56
58
|
| `upload_attachment_from_path` | Uploads a file from a local path — **no file bytes in context**. Use after `write_file` (Filesystem MCP) to attach Claude-generated files. See [Filesystem MCP companion](#filesystem-mcp-companion-stdio-only) below. |
|
|
57
59
|
|
|
60
|
+
### Memory (Pro, feature 0031)
|
|
61
|
+
|
|
62
|
+
Short, atomic memories about you that an agent can recall in later sessions.
|
|
63
|
+
Each memory is encrypted on your machine under your data key, bound to its id
|
|
64
|
+
and revision (AES-GCM with AAD `sqn-memory-v2:<id>:<revision>`), so the server
|
|
65
|
+
stores only ciphertext. Someone holding only your API key cannot put an older
|
|
66
|
+
version back, or another memory's ciphertext in its place: it does not open.
|
|
67
|
+
**What this does not stop:** the server, or whoever operates it, returning a
|
|
68
|
+
whole older row (its ciphertext *and* its older revision). That opens, and
|
|
69
|
+
undoes later changes, because this MCP keeps no record of the revisions it has
|
|
70
|
+
seen. The app (PR 1b) will notice a revision lower than the one it cached; the
|
|
71
|
+
MCP does not (R-038).
|
|
72
|
+
|
|
73
|
+
| Tool | Description |
|
|
74
|
+
|------|-------------|
|
|
75
|
+
| `search_memory` | Finds memories by the words in their text, entity and tags: BM25 over the decrypted set, times a recency decay per category (half-life 30 days for `project_state`, a year for preferences and mental models, two years for entity facts), with the app's trigram matching. The decrypted list is reused for up to 60 s, and re-read after any write from this process. Reads every page of `GET /public/memory` and searches the decrypted set on your machine; it never asks the server for one memory, so the server cannot tell which ones were consulted. Leaves out superseded and invalidated memories unless asked. A record that fails to decrypt or verify is skipped and counted (`unverifiable`), never shown. |
|
|
76
|
+
| `remember_memory` | Saves one memory (`text`, `category`, `entity`, optional `tags`, `sourceNoteId`). It is never pinned: only you pin, in the app. Returns near-duplicates so the agent can supersede instead of adding a copy. |
|
|
77
|
+
| `supersede_memory` | Replaces an active memory: the old one is kept, marked superseded and linked to the new one. Guarded by the memory's revision, so a concurrent change is refused (409), not overwritten. Both records are written unpinned, even if the old one was pinned. |
|
|
78
|
+
| `invalidate_memory` | Marks an active memory as wrong. The text is kept; the memory is written unpinned. |
|
|
79
|
+
| `get_memory_profile` | No inputs. The memories you have pinned (payload field `pinned`; only you pin, in the app), active ones only, most recently updated first (ties by id), cut at whole memories once text plus entity passes about 300 tokens (characters ÷ 4); the first is always included. Each item carries `needsReview`, `reviewedAt` and `provenance`. With nothing pinned it says that only you pin, in the app. Also the resource `memory://profile`, with the same content, and named in the server's instructions so an agent loads it at the start of a session. Reads only the list, like `search_memory`. |
|
|
80
|
+
| `import_memories_from_note` | Turns a note (`noteId`) into candidates, one per list item and one per paragraph, with the heading above as the entity and `category` (default `entity_fact`) on each. `dryRun` defaults to `true` (returns candidates only); with `dryRun: false` it writes up to 100. The note is never changed. |
|
|
81
|
+
|
|
82
|
+
- **Every agent write is live and flagged.** It sets `needsReview: true`,
|
|
83
|
+
`author: 'agent'` and `flaggedAt` inside the ciphertext. `flaggedAt` (ISO
|
|
84
|
+
8601 UTC) is when the memory was flagged: the app's review badge counts 24
|
|
85
|
+
hours from it (ADR-0419 §4) and clears it to `null` when you review. A new
|
|
86
|
+
memory gets now. Superseding or invalidating a memory that is already
|
|
87
|
+
flagged keeps its time, and a supersede's new memory inherits it, so an
|
|
88
|
+
agent cannot restart the clock. It is optional in the payload (`v` stays
|
|
89
|
+
1), so older memories without it still open. A flagged memory is still searched
|
|
90
|
+
and used: the flag is for you, not a gate (ADR-0418 §5). The flag is set by
|
|
91
|
+
the MCP, so a modified client could leave it off (R-038).
|
|
92
|
+
- **Agents cannot delete or pin memories.** No tool sends a `DELETE`, and
|
|
93
|
+
`/public/memory` has no delete route. No agent write is pinned, so a pinned
|
|
94
|
+
memory that an agent supersedes or invalidates leaves your profile until
|
|
95
|
+
you pin it again (product owner, 2026-10-12). You delete and pin them in
|
|
96
|
+
the app.
|
|
97
|
+
- **Search matches ASCII letters and digits only**, as the app's search does:
|
|
98
|
+
accented and non-Latin letters are ignored, so text written only in them
|
|
99
|
+
cannot be found, and near-duplicate detection cannot see it. A query with no
|
|
100
|
+
searchable characters says so.
|
|
101
|
+
- **Up to 60 s stale.** Search and the profile reuse the decrypted list for up
|
|
102
|
+
to 60 s, so a change made in the app, or a revoked key, can take up to 60 s
|
|
103
|
+
to show in a running session (product owner, 2026-10-12; R-036). Writes
|
|
104
|
+
from this process, and a rejected key, clear it at once.
|
|
105
|
+
- **Memory text is data, not instructions.** The server instructions and every
|
|
106
|
+
memory tool description tell the agent so, and to treat a memory as
|
|
107
|
+
unverified while `needsReview` is true. Results carry `author`,
|
|
108
|
+
`provenance` (`via`, the client's self-asserted name, capped at 100
|
|
109
|
+
characters, and the tool) and `reviewedAt`.
|
|
110
|
+
- **Limits.** An account holds up to 5,000 memories and 4 MiB of memory
|
|
111
|
+
ciphertext; past either, writes return a clear "limit reached" error. Writes
|
|
112
|
+
are limited to 120 a minute per user, per route, per Lambda container
|
|
113
|
+
(ADR-0418 §9b, R-042), so not an exact account-wide limit.
|
|
114
|
+
- **Errors and logs never contain memory text.** Log lines carry the tool name,
|
|
115
|
+
ids, counts and HTTP status only.
|
|
116
|
+
- **stdio only.** The memory tools exist only in this stdio package
|
|
117
|
+
(ADR-0421 removed the hosted HTTP server).
|
|
118
|
+
|
|
119
|
+
#### Semantic memory search (opt-in)
|
|
120
|
+
|
|
121
|
+
This package's `search_memory` is lexical. The separate package
|
|
122
|
+
[`@squirrelnotes.app/mcp-embeddings`](https://www.npmjs.com/package/@squirrelnotes.app/mcp-embeddings) is this same
|
|
123
|
+
server (built from the same source and published from the same commit) with a
|
|
124
|
+
local embedding model, so `search_memory` also finds memories by meaning. To
|
|
125
|
+
opt in, change `args` to `["-y", "@squirrelnotes.app/mcp-embeddings"]` and add
|
|
126
|
+
`"ONNXRUNTIME_NODE_INSTALL": "skip"` to `env`.
|
|
127
|
+
|
|
128
|
+
- The model runs in the MCP process on your machine. Memory text is not sent
|
|
129
|
+
anywhere to be embedded. Search results still go to your agent.
|
|
130
|
+
- Vectors are kept only in an AES-256-GCM file in your OS cache directory,
|
|
131
|
+
mode `0600`. They are never sent, logged or returned.
|
|
132
|
+
- Tools, inputs and output keys are identical. If the model is refused or
|
|
133
|
+
cannot load, search is this package's lexical search.
|
|
134
|
+
|
|
135
|
+
This package has no ML dependency: `onnxruntime-node` is installed only by the
|
|
136
|
+
companion (ADR-0442).
|
|
137
|
+
|
|
58
138
|
---
|
|
59
139
|
|
|
60
140
|
## Filesystem MCP Companion (STDIO only)
|
|
@@ -293,8 +373,20 @@ tail -f /tmp/squirrel-mcp.log
|
|
|
293
373
|
|
|
294
374
|
#### What's logged
|
|
295
375
|
|
|
296
|
-
|
|
297
|
-
|
|
376
|
+
Each line goes to stderr and, when `SQUIRREL_LOG_FILE` is set, is also appended to that file.
|
|
377
|
+
|
|
378
|
+
- `upload_attachment_from_path` progress: **the local file path it reads**, which can contain a plaintext filename; the file's size and MIME type; the new attachment id; the S3 PUT status code; and the confirmed attachment id. Leave `SQUIRREL_LOG_FILE` unset if those paths should not be kept on disk.
|
|
379
|
+
- Memory tools: the tool name, memory ids, counts (memories searched, results, unverifiable records, imports created, profile items and omitted) and, on failure, the HTTP status or `network`. Never memory text, entities, tags or reasons.
|
|
380
|
+
- With the embeddings companion only: one start-up line saying whether local
|
|
381
|
+
embeddings are ready or were not loaded (with a reason and, at most, a file
|
|
382
|
+
name), and, once, a runtime or vector-file failure. Never a vector or a query.
|
|
383
|
+
- Fatal startup errors: missing `SQUIRREL_API_KEY`, `SQUIRREL_PASSPHRASE` or `SQUIRREL_API_BASE_URL`, or a failure to connect the stdio transport.
|
|
384
|
+
|
|
385
|
+
Not logged:
|
|
386
|
+
|
|
387
|
+
- An S3 PUT failure. The tool throws it, with S3's status and response body, and the MCP client shows it as the tool's error. The log has only the status line above.
|
|
388
|
+
- A wrong passphrase. It is no longer caught at startup: the key is derived on the first tool call, and a wrong passphrase fails that call (and every later one) with an error returned to the client.
|
|
389
|
+
- The passphrase, the API key, the data key and any decrypted content.
|
|
298
390
|
|
|
299
391
|
---
|
|
300
392
|
|
|
@@ -306,3 +398,4 @@ tail -f /tmp/squirrel-mcp.log
|
|
|
306
398
|
| `SQUIRREL_API_KEY` | ✅ | Your API key from Settings → API Key (requires Pro tier) |
|
|
307
399
|
| `SQUIRREL_PASSPHRASE` | ✅ | Your encryption passphrase — never leaves your machine |
|
|
308
400
|
| `SQUIRREL_LOG_FILE` | — | Absolute path to a log file. When set, all stderr output is also appended to this file. |
|
|
401
|
+
| `SQUIRREL_MEMORY_EMBEDDINGS`, `SQUIRREL_MEMORY_MODEL_DIR`, `SQUIRREL_MEMORY_CACHE_DIR` | — | Read only by the opt-in embeddings companion (`@squirrelnotes.app/mcp-embeddings`); see its README. |
|