@squirrelnotes.app/mcp
v1.31.2
Published
MCP stdio CLI for Squirrel Notes — bridges AI agents to the encrypted note-taking API
Readme
Squirrel Notes MCP Server
An MCP (Model Context Protocol) server that lets AI agents (Claude Desktop, Cursor, etc.) read and write your Squirrel Notes, with full end-to-end decryption.
⚠️ Security Caveat — Read This First
The MCP server necessarily decrypts your notes — that is its job. The security implications depend on which transport you use:
stdio (local process) — recommended
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.
| Risk | Mitigation | |------|------------| | Passphrase in config file | Stored on disk in a local config file; protect it with OS user permissions and full-disk encryption | | Passphrase in process memory | Env var; cleared when process exits | | Decrypted content visible to AI | This is the intended behaviour — AI agents need to read your notes |
Remote HTTP (Lambda)
Your passphrase is sent over HTTPS to a Lambda function on every call.
| Risk | Mitigation | |------|------------| | Passphrase sent over the network | HTTPS-only (API Gateway always TLS) | | Passphrase in server RAM during invocation | Stack-scoped variable; never assigned to module state; GC'd after invocation | | Passphrase in CloudWatch logs | Lambda code never logs headers | | Lambda container reuse retaining secrets | Passphrase is NOT module-scope — reused containers hold no residual secret |
You must trust: AWS Lambda, the self-hosted MCP server code, and anyone with access to your CloudWatch logs.
If this trade-off is not acceptable, use stdio transport instead.
Tools
Notes
| Tool | Description |
|------|-------------|
| list_notes | Returns all notes with decrypted titles, tags, and metadata. Supports optional collectionId, tagId, pinned filters. |
| get_recent_notes | Returns the N most-recently updated notes (default 10). Fast — titles only. |
| get_note | Returns a single note with decrypted title + full content. |
| create_note | Creates a note with encrypted title + content. |
| update_note | Updates title, content, or pinnedAt on a note. |
| delete_note | Soft-deletes a note (recoverable). |
| pin_note | Pins or unpins a note (appears at top of sidebar). |
| append_to_note | Appends markdown content to an existing note. Avoids race conditions vs get → update. |
| set_note_tags | Replaces all tags on a note. |
| search_notes | Fast title-only search across all notes. |
| search_note_content | Full-content search (slow — decrypts all notes). Use only when title search fails. |
| move_note_to_collection | Moves a note to a collection (or to scratchpad if collectionId is null). |
Saved Searches (Pro)
| Tool | Description |
|------|-------------|
| list_saved_searches | Returns saved searches with decrypted names and smart-query strings. |
| create_saved_search | Creates a saved search from plaintext name + query (encrypted before API call). |
| update_saved_search | Updates saved search name and/or query. |
| delete_saved_search | Deletes a saved search (soft-delete server-side). |
Attachments
| Tool | Transport | Description |
|------|-----------|-------------|
| list_attachments | All | Lists all attachments for a note with decrypted filenames and metadata. |
| get_attachment_download_url | All | Returns a presigned S3 download URL (expires in 5 minutes). |
| delete_attachment | All | Permanently deletes an attachment from S3 and the database. |
| 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 below. |
Filesystem MCP Companion (STDIO only)
Combining the Squirrel Notes MCP server with the MCP Filesystem server enables a two-step pipeline for attaching files generated by Claude — without putting file bytes in the context window:
Claude generates content
│
▼ write_file (Filesystem MCP) ──► file lands on disk
│
▼ upload_attachment_from_path (Squirrel MCP)
reads bytes, encrypts, uploads to S3
(only a path string in context)Claude Desktop configuration
Add both servers to claude_desktop_config.json:
{
"mcpServers": {
"squirrel-notes": {
"command": "npx",
"args": ["-y", "@squirrelnotes.app/mcp"],
"env": {
"SQUIRREL_API_BASE_URL": "https://api.squirrelnotes.app",
"SQUIRREL_API_KEY": "sqn_your_api_key",
"SQUIRREL_PASSPHRASE": "your-passphrase",
"SQUIRREL_UPLOAD_ROOT": "/tmp"
}
},
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
"env": {}
}
}
}
SQUIRREL_UPLOAD_ROOTrestricts which directoriesupload_attachment_from_pathmay read from. Defaults toos.tmpdir()—/tmpon 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.
Example workflows
Attach a Claude-generated CSV report to a note:
"Create a monthly expense summary as a CSV and attach it to my Finances note."
- Claude generates the CSV content →
write_file("/tmp/expenses.csv", content)(Filesystem MCP) - Claude calls
upload_attachment_from_pathwithlocalPath="/tmp/expenses.csv"(Squirrel MCP)
Attach an xlsx file generated by a script:
"Write a Python script to generate an xlsx report and attach it to my Analytics note."
- Claude writes the script to
/tmp/generate_report.py→ executes it →report.xlsxis written to/tmp/ - Claude calls
upload_attachment_from_pathwithlocalPath="/tmp/report.xlsx"
Bulk-import Markdown files from disk:
"Import all the markdown files from /tmp/obsidian-export/ as notes in my Archive collection."
- Claude calls
list_directory("/tmp/obsidian-export/")(Filesystem MCP) to enumerate files - For each
.mdfile:read_file(path)→create_notewith the content (Squirrel MCP)
Export a note to disk:
"Export my project roadmap note as a Markdown file to /tmp/roadmap.md."
- Claude calls
get_note(id)(Squirrel MCP) to read the note - Claude calls
write_file("/tmp/roadmap.md", content)(Filesystem MCP)
Every request requires two headers:
Authorization: Bearer sqn_<api_key>
X-Passphrase: <your_passphrase>Generate an API key in Squirrel Notes → Settings → API Key (requires Pro tier).
Neither header is ever stored, cached, or logged by the server.
Setup
Which setup should I use?
- Claude Desktop → use stdio (recommended) — no OAuth, passphrase stays on your machine
- API automation / Cursor / other agents → use Remote HTTP (Lambda)
Setup — Claude Desktop (stdio) ✅ Recommended
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.
Option A — npx (zero install, recommended)
No download or build step required. npx fetches the latest published version automatically on first run and caches it locally.
1. Configure Claude Desktop
Open ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows) and add:
{
"mcpServers": {
"squirrel-notes": {
"command": "npx",
"args": ["-y", "@squirrelnotes.app/mcp"],
"env": {
"SQUIRREL_API_BASE_URL": "https://api.squirrelnotes.app",
"SQUIRREL_API_KEY": "sqn_<your_api_key>",
"SQUIRREL_PASSPHRASE": "<your_passphrase>"
}
}
}
}2. Restart Claude Desktop
Quit and reopen Claude Desktop. The Squirrel Notes MCP server will start automatically.
3. Verify
Ask Claude: "List my Squirrel Notes" — you should see your note titles.
Option B — build from source (local development / pinned version)
Use this if you want to run a specific local build or contribute to the server code.
cd mcp
npm install
npm run build:stdio
# Produces dist/stdio-handler.jsThen reference the built file directly:
{
"mcpServers": {
"squirrel-notes": {
"command": "node",
"args": ["/absolute/path/to/squirrel-notes/mcp/dist/stdio-handler.js"],
"env": {
"SQUIRREL_API_BASE_URL": "https://api.squirrelnotes.app",
"SQUIRREL_API_KEY": "sqn_<your_api_key>",
"SQUIRREL_PASSPHRASE": "<your_passphrase>"
}
}
}
}Windows: Replace the path with
C:\\path\\to\\mcp\\dist\\stdio-handler.js(double backslashes).
Setup — Remote HTTP (Lambda)
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.
1. Deploy with CDK
cd infra
npx cdk deploy SquirrelMcpThis creates:
- A Lambda function running the MCP server Docker image
- An HTTP API Gateway at
https://<id>.execute-api.<region>.amazonaws.com/mcp
The deployed URL is printed as a CDK output (McpApiUrl).
2. Configure your client
{
"mcpServers": {
"squirrel-notes": {
"url": "https://<your-mcp-api-url>/mcp",
"headers": {
"Authorization": "Bearer sqn_<your_api_key>",
"X-Passphrase": "<your_passphrase>"
}
}
}
}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.
3. Verify
Ask Claude: "List my Squirrel Notes" — you should see decrypted note titles.
Common Workflows
Once configured, you can speak to Claude naturally. Some examples:
| What you say | What Claude does |
|---|---|
| "What have I been working on lately?" | get_recent_notes → lists last 10 updated notes |
| "Show me everything in my Work collection" | list_collections → list_notes(collectionId: ...) |
| "Search my notes for 'project roadmap'" | search_notes (title match) or search_note_content |
| "Create a note called 'Meeting Notes' in my Work collection" | list_collections → create_note(collectionId: ...) |
| "Add today's standup to my Daily Log note" | search_notes("Daily Log") → append_to_note(id, ...) |
| "Pin my TODO note" | search_notes("TODO") → pin_note(id, true) |
| "Tag my 'Project Roadmap' note as urgent" | list_tags → search_notes → set_note_tags |
| "Show me all my pinned notes" | list_notes(pinned: true) |
| "Show me all notes tagged 'urgent'" | list_tags → list_notes(tagId: ...) |
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.
Local Development
cd mcp
npm install
npm run devThis 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.
Smoke test with curl:
curl -s http://localhost:3002 -X POST \
-H "Authorization: Bearer sqn_<your_api_key>" \
-H "X-Passphrase: <your_passphrase>" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | jq .Or use the MCP Inspector for a browser UI:
npx @modelcontextprotocol/inspector http://localhost:3002Debugging / Getting Logs
stdio transport (Claude Desktop)
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.
Where to find the logs
macOS / Linux Claude Desktop captures stderr from each MCP server in a dedicated log file:
~/Library/Logs/Claude/mcp-server-squirrel-notes.log # macOSOpen this file in any text editor or tail it in a terminal:
tail -f ~/Library/Logs/Claude/mcp-server-squirrel-notes.logWindows
%APPDATA%\Claude\logs\mcp-server-squirrel-notes.logWrite logs to a custom file
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:
{
"mcpServers": {
"squirrel-notes": {
"command": "npx",
"args": ["-y", "@squirrelnotes.app/mcp"],
"env": {
"SQUIRREL_API_BASE_URL": "https://api.squirrelnotes.app",
"SQUIRREL_API_KEY": "sqn_<your_api_key>",
"SQUIRREL_PASSPHRASE": "<your_passphrase>",
"SQUIRREL_LOG_FILE": "/tmp/squirrel-mcp.log"
}
}
}
}Then in a terminal:
tail -f /tmp/squirrel-mcp.logWhat's logged
- S3 PUT failures (HTTP status + response body from S3)
- Fatal startup errors (missing env vars, wrong passphrase)
Environment Variables
stdio transport
| Variable | Required | Description |
|----------|----------|-------------|
| SQUIRREL_API_BASE_URL | ✅ | Base URL of the Squirrel Notes backend API (e.g. https://api.squirrelnotes.app) |
| SQUIRREL_API_KEY | ✅ | Your API key from Settings → API Key (requires Pro tier) |
| SQUIRREL_PASSPHRASE | ✅ | Your encryption passphrase — never leaves your machine |
| SQUIRREL_LOG_FILE | — | Absolute path to a log file. When set, all stderr output is also appended to this file. |
Lambda (HTTP) transport
| Variable | Description |
|----------|-------------|
| SQUIRREL_API_BASE_URL | Base URL of the Squirrel Notes backend API |
For Lambda, the API key and passphrase are passed per-request via Authorization and X-Passphrase headers — not environment variables.
