@squirrelnotes.app/mcp 1.27.3

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 ADDED
@@ -0,0 +1,378 @@
1
+ # Squirrel Notes MCP Server
2
+
3
+ An [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server that lets AI agents (Claude Desktop, Cursor, etc.) read and write your Squirrel Notes, with full end-to-end decryption.
4
+
5
+ ---
6
+
7
+ ## ⚠️ Security Caveat — Read This First
8
+
9
+ The MCP server necessarily decrypts your notes — that is its job. The security implications depend on which transport you use:
10
+
11
+ ### stdio (local process) — recommended
12
+
13
+ The passphrase lives in `claude_desktop_config.json` on your machine and is passed as an env var to the local process. It never leaves your machine, and decryption happens locally — similar to the standard Squirrel Notes app, where the passphrase is entered locally in the browser and not sent to the server. Compared with the Remote HTTP/Lambda transport below, stdio avoids sending your passphrase over the network, at the cost of storing it in a local config file.
14
+
15
+ | Risk | Mitigation |
16
+ |------|------------|
17
+ | Passphrase in config file | Stored on disk in a local config file; protect it with OS user permissions and full-disk encryption |
18
+ | Passphrase in process memory | Env var; cleared when process exits |
19
+ | Decrypted content visible to AI | This is the intended behaviour — AI agents need to read your notes |
20
+
21
+ ### Remote HTTP (Lambda)
22
+
23
+ Your passphrase is sent over HTTPS to a Lambda function on every call.
24
+
25
+ | Risk | Mitigation |
26
+ |------|------------|
27
+ | Passphrase sent over the network | HTTPS-only (API Gateway always TLS) |
28
+ | Passphrase in server RAM during invocation | Stack-scoped variable; never assigned to module state; GC'd after invocation |
29
+ | Passphrase in CloudWatch logs | Lambda code never logs headers |
30
+ | Lambda container reuse retaining secrets | Passphrase is NOT module-scope — reused containers hold no residual secret |
31
+
32
+ **You must trust:** AWS Lambda, the self-hosted MCP server code, and anyone with access to your CloudWatch logs.
33
+
34
+ If this trade-off is not acceptable, use stdio transport instead.
35
+
36
+ ---
37
+
38
+ ## Tools
39
+
40
+ ### Notes
41
+
42
+ | Tool | Description |
43
+ |------|-------------|
44
+ | `list_notes` | Returns all notes with decrypted titles, tags, and metadata. Supports optional `collectionId`, `tagId`, `pinned` filters. |
45
+ | `get_recent_notes` | Returns the N most-recently updated notes (default 10). Fast — titles only. |
46
+ | `get_note` | Returns a single note with decrypted title + full content. |
47
+ | `create_note` | Creates a note with encrypted title + content. |
48
+ | `update_note` | Updates title, content, or pinnedAt on a note. |
49
+ | `delete_note` | Soft-deletes a note (recoverable). |
50
+ | `pin_note` | Pins or unpins a note (appears at top of sidebar). |
51
+ | `append_to_note` | Appends markdown content to an existing note. Avoids race conditions vs get → update. |
52
+ | `set_note_tags` | Replaces all tags on a note. |
53
+ | `search_notes` | Fast title-only search across all notes. |
54
+ | `search_note_content` | Full-content search (slow — decrypts all notes). Use only when title search fails. |
55
+ | `move_note_to_collection` | Moves a note to a collection (or to scratchpad if collectionId is null). |
56
+
57
+ ### Saved Searches (Pro)
58
+
59
+ | Tool | Description |
60
+ |------|-------------|
61
+ | `list_saved_searches` | Returns saved searches with decrypted names and smart-query strings. |
62
+ | `create_saved_search` | Creates a saved search from plaintext name + query (encrypted before API call). |
63
+ | `update_saved_search` | Updates saved search name and/or query. |
64
+ | `delete_saved_search` | Deletes a saved search (soft-delete server-side). |
65
+
66
+ ### Attachments
67
+
68
+ | Tool | Transport | Description |
69
+ |------|-----------|-------------|
70
+ | `list_attachments` | All | Lists all attachments for a note with decrypted filenames and metadata. |
71
+ | `get_attachment_download_url` | All | Returns a presigned S3 download URL (expires in 5 minutes). |
72
+ | `delete_attachment` | All | Permanently deletes an attachment from S3 and the database. |
73
+ | `upload_attachment_from_path` | STDIO only | 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. |
74
+
75
+ ---
76
+
77
+ ## Filesystem MCP Companion (STDIO only)
78
+
79
+ Combining the Squirrel Notes MCP server with the [MCP Filesystem server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem) enables a two-step pipeline for attaching files generated by Claude — without putting file bytes in the context window:
80
+
81
+ ```
82
+ Claude generates content
83
+ │
84
+ ▼ write_file (Filesystem MCP) ──► file lands on disk
85
+ │
86
+ ▼ upload_attachment_from_path (Squirrel MCP)
87
+ reads bytes, encrypts, uploads to S3
88
+ (only a path string in context)
89
+ ```
90
+
91
+ ### Claude Desktop configuration
92
+
93
+ Add both servers to `claude_desktop_config.json`:
94
+
95
+ ```json
96
+ {
97
+ "mcpServers": {
98
+ "squirrel-notes": {
99
+ "command": "node",
100
+ "args": ["/path/to/squirrel-notes-mcp/dist/stdio-handler.js"],
101
+ "env": {
102
+ "SQUIRREL_API_BASE_URL": "https://api.squirrelnotes.app",
103
+ "SQUIRREL_API_KEY": "sqn_your_api_key",
104
+ "SQUIRREL_PASSPHRASE": "your-passphrase",
105
+ "SQUIRREL_UPLOAD_ROOT": "/tmp"
106
+ }
107
+ },
108
+ "filesystem": {
109
+ "command": "npx",
110
+ "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
111
+ "env": {}
112
+ }
113
+ }
114
+ }
115
+ ```
116
+
117
+ > **`SQUIRREL_UPLOAD_ROOT`** restricts which directories `upload_attachment_from_path` may read from. Defaults to `os.tmpdir()` — `/tmp` on macOS/Linux, `%TEMP%` equivalent on Windows. Set to a comma-separated list for multiple allowed roots (e.g. `"/tmp,/Users/you/Downloads"`). This is a security guardrail — without it, a prompt-injection attack could trick Claude into uploading files from anywhere on disk (e.g. `~/.ssh/id_rsa`). Note: symlinks inside an allowed root that point outside it are not followed during the check — use OS-level controls if that is a concern.
118
+
119
+ ### Example workflows
120
+
121
+ **Attach a Claude-generated CSV report to a note:**
122
+ > "Create a monthly expense summary as a CSV and attach it to my Finances note."
123
+
124
+ 1. Claude generates the CSV content → `write_file("/tmp/expenses.csv", content)` (Filesystem MCP)
125
+ 2. Claude calls `upload_attachment_from_path` with `localPath="/tmp/expenses.csv"` (Squirrel MCP)
126
+
127
+ **Attach an xlsx file generated by a script:**
128
+ > "Write a Python script to generate an xlsx report and attach it to my Analytics note."
129
+
130
+ 1. Claude writes the script to `/tmp/generate_report.py` → executes it → `report.xlsx` is written to `/tmp/`
131
+ 2. Claude calls `upload_attachment_from_path` with `localPath="/tmp/report.xlsx"`
132
+
133
+ **Bulk-import Markdown files from disk:**
134
+ > "Import all the markdown files from /tmp/obsidian-export/ as notes in my Archive collection."
135
+
136
+ 1. Claude calls `list_directory("/tmp/obsidian-export/")` (Filesystem MCP) to enumerate files
137
+ 2. For each `.md` file: `read_file(path)` → `create_note` with the content (Squirrel MCP)
138
+
139
+ **Export a note to disk:**
140
+ > "Export my project roadmap note as a Markdown file to /tmp/roadmap.md."
141
+
142
+ 1. Claude calls `get_note(id)` (Squirrel MCP) to read the note
143
+ 2. Claude calls `write_file("/tmp/roadmap.md", content)` (Filesystem MCP)
144
+
145
+ Every request requires two headers:
146
+
147
+ ```
148
+ Authorization: Bearer sqn_<api_key>
149
+ X-Passphrase: <your_passphrase>
150
+ ```
151
+
152
+ Generate an API key in Squirrel Notes → Settings → API Key (requires Pro tier).
153
+
154
+ Neither header is ever stored, cached, or logged by the server.
155
+
156
+ ---
157
+
158
+ ## Setup
159
+
160
+ > **Which setup should I use?**
161
+ > - **Claude Desktop** → use **stdio (recommended)** — no OAuth, passphrase stays on your machine
162
+ > - **API automation / Cursor / other agents** → use **Remote HTTP (Lambda)**
163
+
164
+ ---
165
+
166
+ ### Setup — Claude Desktop (stdio) ✅ Recommended
167
+
168
+ The stdio transport runs the MCP server as a local process on your machine. No OAuth, no network headers, no AWS account needed. Your passphrase is set as an env var in the config file and **never leaves your machine**.
169
+
170
+ #### 1. Build the stdio handler
171
+
172
+ ```bash
173
+ cd mcp
174
+ npm install
175
+ npm run build:stdio
176
+ # Produces dist/stdio-handler.js
177
+ ```
178
+
179
+ #### 2. Configure Claude Desktop
180
+
181
+ Open `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) and add:
182
+
183
+ ```json
184
+ {
185
+ "mcpServers": {
186
+ "squirrel-notes": {
187
+ "command": "node",
188
+ "args": ["/absolute/path/to/squirrel-notes/mcp/dist/stdio-handler.js"],
189
+ "env": {
190
+ "SQUIRREL_API_BASE_URL": "https://api.squirrelnotes.app",
191
+ "SQUIRREL_API_KEY": "sqn_<your_api_key>",
192
+ "SQUIRREL_PASSPHRASE": "<your_passphrase>"
193
+ }
194
+ }
195
+ }
196
+ }
197
+ ```
198
+
199
+ > **Windows:** Use `"command": "node"` and replace the path with `C:\\path\\to\\mcp\\dist\\stdio-handler.js` (double backslashes).
200
+
201
+ #### 3. Restart Claude Desktop
202
+
203
+ Quit and reopen Claude Desktop. The Squirrel Notes MCP server will start automatically.
204
+
205
+ #### 4. Verify
206
+
207
+ Ask Claude: *"List my Squirrel Notes"* — you should see your note titles.
208
+
209
+ ---
210
+
211
+ ### Setup — Remote HTTP (Lambda)
212
+
213
+ Use this if you want to connect agents or tools that support remote HTTP MCP endpoints (Cursor, custom automations, etc.). This requires deploying to AWS and sending the passphrase over HTTPS on every call.
214
+
215
+ #### 1. Deploy with CDK
216
+
217
+ ```bash
218
+ cd infra
219
+ npx cdk deploy SquirrelMcp
220
+ ```
221
+
222
+ This creates:
223
+ - A Lambda function running the MCP server Docker image
224
+ - An HTTP API Gateway at `https://<id>.execute-api.<region>.amazonaws.com/mcp`
225
+
226
+ The deployed URL is printed as a CDK output (`McpApiUrl`).
227
+
228
+ #### 2. Configure your client
229
+
230
+ ```json
231
+ {
232
+ "mcpServers": {
233
+ "squirrel-notes": {
234
+ "url": "https://<your-mcp-api-url>/mcp",
235
+ "headers": {
236
+ "Authorization": "Bearer sqn_<your_api_key>",
237
+ "X-Passphrase": "<your_passphrase>"
238
+ }
239
+ }
240
+ }
241
+ }
242
+ ```
243
+
244
+ > **Note:** Newer versions of Claude Desktop enforce OAuth 2.0 for remote HTTP servers and will reject this config. Use the stdio setup above for Claude Desktop.
245
+
246
+ #### 3. Verify
247
+
248
+ Ask Claude: *"List my Squirrel Notes"* — you should see decrypted note titles.
249
+
250
+ ---
251
+
252
+ ## Common Workflows
253
+
254
+ Once configured, you can speak to Claude naturally. Some examples:
255
+
256
+ | What you say | What Claude does |
257
+ |---|---|
258
+ | *"What have I been working on lately?"* | `get_recent_notes` → lists last 10 updated notes |
259
+ | *"Show me everything in my Work collection"* | `list_collections` → `list_notes(collectionId: ...)` |
260
+ | *"Search my notes for 'project roadmap'"* | `search_notes` (title match) or `search_note_content` |
261
+ | *"Create a note called 'Meeting Notes' in my Work collection"* | `list_collections` → `create_note(collectionId: ...)` |
262
+ | *"Add today's standup to my Daily Log note"* | `search_notes("Daily Log")` → `append_to_note(id, ...)` |
263
+ | *"Pin my TODO note"* | `search_notes("TODO")` → `pin_note(id, true)` |
264
+ | *"Tag my 'Project Roadmap' note as urgent"* | `list_tags` → `search_notes` → `set_note_tags` |
265
+ | *"Show me all my pinned notes"* | `list_notes(pinned: true)` |
266
+ | *"Show me all notes tagged 'urgent'"* | `list_tags` → `list_notes(tagId: ...)` |
267
+
268
+ > **Tip for Claude Desktop:** Add a system prompt in your Claude Project describing your note structure (e.g. "I use a collection called 'Work' for work notes and 'Personal' for personal notes, and tags like 'urgent' and 'later' for priority"). This reduces the number of lookup calls Claude needs to make.
269
+
270
+ ---
271
+
272
+ ## Local Development
273
+
274
+ ```bash
275
+ cd mcp
276
+ npm install
277
+ npm run dev
278
+ ```
279
+
280
+ This starts an HTTP MCP server at `http://localhost:3002` pointing at the local backend (`http://localhost:3001`). No build step needed — `tsx` runs TypeScript directly.
281
+
282
+ Smoke test with curl:
283
+
284
+ ```bash
285
+ curl -s http://localhost:3002 -X POST \
286
+ -H "Authorization: Bearer sqn_<your_api_key>" \
287
+ -H "X-Passphrase: <your_passphrase>" \
288
+ -H "Content-Type: application/json" \
289
+ -H "Accept: application/json, text/event-stream" \
290
+ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | jq .
291
+ ```
292
+
293
+ Or use the MCP Inspector for a browser UI:
294
+
295
+ ```bash
296
+ npx @modelcontextprotocol/inspector http://localhost:3002
297
+ ```
298
+
299
+ ---
300
+
301
+ ## Debugging / Getting Logs
302
+
303
+ ### stdio transport (Claude Desktop)
304
+
305
+ The MCP protocol uses **stdout** for JSON-RPC messages, so all diagnostic output goes to **stderr** instead. The MCP server writes errors and key upload steps to stderr automatically.
306
+
307
+ #### Where to find the logs
308
+
309
+ **macOS / Linux**
310
+ Claude Desktop captures stderr from each MCP server in a dedicated log file:
311
+
312
+ ```
313
+ ~/Library/Logs/Claude/mcp-server-squirrel-notes.log # macOS
314
+ ```
315
+
316
+ Open this file in any text editor or tail it in a terminal:
317
+
318
+ ```bash
319
+ tail -f ~/Library/Logs/Claude/mcp-server-squirrel-notes.log
320
+ ```
321
+
322
+ **Windows**
323
+ ```
324
+ %APPDATA%\Claude\logs\mcp-server-squirrel-notes.log
325
+ ```
326
+
327
+ #### Write logs to a custom file
328
+
329
+ Set `SQUIRREL_LOG_FILE` to an absolute path in your `claude_desktop_config.json` to capture all stderr output to a file of your choice:
330
+
331
+ ```json
332
+ {
333
+ "mcpServers": {
334
+ "squirrel-notes": {
335
+ "command": "node",
336
+ "args": ["/absolute/path/to/mcp/dist/stdio-handler.js"],
337
+ "env": {
338
+ "SQUIRREL_API_BASE_URL": "https://api.squirrelnotes.app",
339
+ "SQUIRREL_API_KEY": "sqn_<your_api_key>",
340
+ "SQUIRREL_PASSPHRASE": "<your_passphrase>",
341
+ "SQUIRREL_LOG_FILE": "/tmp/squirrel-mcp.log"
342
+ }
343
+ }
344
+ }
345
+ }
346
+ ```
347
+
348
+ Then in a terminal:
349
+
350
+ ```bash
351
+ tail -f /tmp/squirrel-mcp.log
352
+ ```
353
+
354
+ #### What's logged
355
+
356
+ - S3 PUT failures (HTTP status + response body from S3)
357
+ - Fatal startup errors (missing env vars, wrong passphrase)
358
+
359
+ ---
360
+
361
+ ## Environment Variables
362
+
363
+ ### stdio transport
364
+
365
+ | Variable | Required | Description |
366
+ |----------|----------|-------------|
367
+ | `SQUIRREL_API_BASE_URL` | ✅ | Base URL of the Squirrel Notes backend API (e.g. `https://api.squirrelnotes.app`) |
368
+ | `SQUIRREL_API_KEY` | ✅ | Your API key from Settings → API Key (requires Pro tier) |
369
+ | `SQUIRREL_PASSPHRASE` | ✅ | Your encryption passphrase — never leaves your machine |
370
+ | `SQUIRREL_LOG_FILE` | — | Absolute path to a log file. When set, all stderr output is also appended to this file. |
371
+
372
+ ### Lambda (HTTP) transport
373
+
374
+ | Variable | Description |
375
+ |----------|-------------|
376
+ | `SQUIRREL_API_BASE_URL` | Base URL of the Squirrel Notes backend API |
377
+
378
+ For Lambda, the API key and passphrase are passed per-request via `Authorization` and `X-Passphrase` headers — **not** environment variables.