Apple Notes MCP
<div align="center">
# Apple Notes MCP
**Let Claude, Cursor, or Codex search and edit your Apple Notes — by meaning, entirely on your Mac.**
<img width="1460" height="395" alt="Screenshot 2026-08-12 at 11 03 56 PM" src="https://github.com/user-attachments/assets/637a0952-e1db-4426-b50d-0d9713ba0e94" />
<img width="738" height="293" alt="Screenshot 2026-08-12 at 11 04 46 PM" src="https://github.com/user-attachments/assets/52cc9f4e-c5da-44ef-98c5-198235afee2d" />
[](LICENSE)
[](https://modelcontextprotocol.io)
</div>
```
You ▸ What did I decide about the pricing model back in spring?
Claude ▸ [search-notes] "Pricing v3" (Apr 14) — you settled on usage-based
with a floor, after rejecting per-seat in "Pricing v2".
```
You didn't name the note. There's no folder called "pricing." No API key was
used, and nothing left your laptop.
---
## Features
### Search finds notes you can't remember the words for
Every note is embedded on-device with `all-MiniLM-L6-v2` into
[LanceDB](https://lancedb.github.io/lancedb/), alongside a full-text index. A
query runs **both** and fuses the two rankings with Reciprocal Rank Fusion.
*Why both:* keyword search fails when you remember the idea but not the phrasing.
Vector search fails on exact strings — an error code, an invoice number, a
person's name. Fusing them ranks a note that scores mid-range on both above one
that spikes on only one, so you don't have to guess which kind of query you're
making.
### Writes that can't quietly destroy a note
Six write tools, with three guarantees:
| Guarantee | Why it matters |
|---|---|
| **Every mutation snapshots the old body to disk first**, and returns the path | An agent editing your notes is unreviewed by definition. You get an undo file even when the edit was wrong. |
| **Ambiguous titles refuse to write** — two notes sharing a title is an error, not a coin flip | Silently overwriting the wrong note is unrecoverable in a way an error message isn't. |
| **`append-note` is a separate tool from `update-note`** | "Add this to my todos" must never replace the todo list. Separating them makes the destructive option an explicit choice. |
### Reading an untrusted note can't execute code
The obvious way to drive Apple Notes is to interpolate a title into a script:
```js
const title = "${title}" // ← arbitrary code execution
```
A title containing `"` closes that literal, and the rest runs as JavaScript for
Automation — shell commands, file reads, exfiltration. Note titles are **not
trusted input**: they arrive from shared notes, mail-to-Notes, imports, and sync
from other devices.
This server removes the vulnerability class instead of escaping around it. The
JXA scripts are **static files** shipped in the package, titles and bodies are
passed as **`argv`** and read inside `run(argv)`, and `subprocess` always runs
with an argument list and **`shell=False`**. No user-controlled text ever enters
any program source. Full review in [SECURITY.md](SECURITY.md).
### Fully local
No API keys, no account, no telemetry. The only network request in the tool's
life is a one-time ~90 MB model download — after that it works on a plane.
### Built to survive macOS updates
Notes are read through Apple's **automation API**, not by parsing the private
`NoteStore.sqlite`. Direct sqlite reads are faster, but Apple reshapes that
schema between releases and stores bodies as gzipped protobuf. Via the automation
API, macOS's own permission prompt is the consent gate and an OS update doesn't
silently corrupt your index.
### Folder scoping everywhere
`get-note`, `update-note`, `append-note`, `backup-note`, and `move-note` all take
an optional `folder`. Pass it and same-titled notes in other folders or iCloud
accounts stop shadowing your target.
---
## Requirements
macOS (Apple Notes and `osascript` are macOS-only) · Python 3.10+ · ~500 MB disk
for the model and its torch dependency.
## Setup
### 1. Install
```bash
uv tool install applenotes-mcp
```
<details>
<summary>No <code>uv</code>? Or prefer pip?</summary>
```bash
brew install uv && uv tool install applenotes-mcp # recommended, isolated
# or pip, inside a virtualenv
python3 -m venv ~/.venvs/notes && ~/.venvs/notes/bin/pip install applenotes-mcp
```
</details>
> [!IMPORTANT]
> The package is **`applenotes-mcp`** — no hyphen after "apple". The name
> `apple-notes-mcp` on PyPI is a different, unrelated project, so
> `uvx apple-notes-mcp` fetches the wrong thing.
### 2. Register it with your client
Every client runs the same command, `uvx applenotes-mcp`. Find your row:
| Client | How |
|---|---|
| **Claude Code** | `claude mcp add apple-notes -- uvx applenotes-mcp` |
| **Claude Code** — plugin *(recommended)* | [see below](#claude-code-plugin) — registers the server **and** teaches Claude to use it |
| **Cursor** | `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (per project) |
| **Codex CLI** | `~/.codex/config.toml` |
| **Claude Desktop** | Settings → Developer → Edit Config |
| **VS Code / Copilot** | `.vscode/mcp.json` |
| **Zed** | `settings.json`, under `context_servers` |
| **Windsurf** | `~/.codeium/windsurf/mcp_config.json` |
Cursor, Claude Desktop, and Windsurf take this exact JSON:
```json
{
"mcpServers": {
"apple-notes": { "command": "uvx", "args": ["applenotes-mcp"] }
}
}
```
Codex uses TOML:
```toml
[mcp_servers.apple-notes]
command = "uvx"
args = ["applenotes-mcp"]
```
VS Code nests under `servers`, Zed under `context_servers`. Ready-to-paste files
for every client are in **[`integrations/`](integrations/)**.
#### Claude Code plugin
One step for the server plus an `apple-notes` skill:
```
/plugin marketplace add ashishakkumar/apple-notes-mcp
/plugin install apple-notes-mcp
```
### 3. Teach your agent to use it well
Optional, but the highest-leverage step. Step 2 connects the tools; this explains
*when* to reach for them — search before asking which note, why `append-note`
beats `update-note`, what to do with an ambiguous title. Without it, agents tend
to ask you for a note title instead of just searching.
| Client | File | Put it in |
|---|---|---|
| Claude Code | [`skills/apple-notes/SKILL.md`](skills/apple-notes/SKILL.md) | ships with the plugin, or copy to `~/.claude/skills/apple-notes/` |
| Cursor | [`integrations/cursor/apple-notes.mdc`](integrations/cursor/apple-notes.mdc) | `.cursor/rules/` |
| Codex & everything else | [`integrations/AGENTS.md`](integrations/AGENTS.md) | append to your `AGENTS.md` |
### 4. Grant permission and index
1. Ask your assistant: **"Index my Apple Notes."**
2. macOS prompts for permission to control Notes. **Approve it** — nothing works
until you do. (Later: System Settings → Privacy & Security → Automation.)
3. The first run downloads the model once, then embeds every note. Seconds for a
few hundred notes, a couple of minutes for thousands.
4. Ask something real: *"What did I write about the Q3 budget?"*
Re-run `index-notes` after adding or editing notes — the index is a snapshot, not
a live view.
<details>
<summary>Verify the connection</summary>
```bash
claude mcp list # Claude Code
# any client — does the server start and speak MCP?
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"t","version":"0"}}}' \
| uvx applenotes-mcp
```
A JSON reply naming `apple-notes` means the server is fine, and any remaining
problem is client config.
</details>
---
## Tools
**Read**
| Tool | Does |
|---|---|
| `search-notes` | Hybrid semantic + full-text search. The one you'll use most. |
| `get-note` | One note in full, by exact title. Optional `folder`. |
| `list-folders` | Every folder name. |
| `list-folder-notes` | Titles inside one folder. |
| `list-notes` | How many notes are indexed. |
| `index-notes` | Rebuild the index from scratch. |
**Write** — each snapshots the old body to `~/.local/share/apple-notes-mcp/backups/` first.
| Tool | Does |
|---|---|
| `append-note` | Add HTML to the end of a note. Prefer this over `update-note`. |
| `update-note` | Replace a note's body wholesale. |
| `create-note` | New note in the default folder. |
| `create-note-in-folder` | New note in a named folder. Fails if it doesn't exist. |
| `move-note` | Move a note to another folder. |
| `backup-note` | Snapshot on demand, before a risky multi-step edit. |
Bodies are HTML on a single line, capped at 2 MB.
## What to know before indexing
- **Indexing reads every note in full.** That's the point, but be deliberate.
- **The index is unencrypted** at `~/.mcp-apple-notes/data`, protected only by
file permissions and FileVault. Treat it as sensitive as your notes.
- **Prompt injection is inherent to RAG.** A note can contain text aimed at the
assistant reading it. No MCP server can neutralize that.
| Path | Contents |
|---|---|
| `~/.mcp-apple-notes/data` | LanceDB vector + full-text index |
| `~/.local/share/apple-notes-mcp/backups/` | Pre-mutation snapshots (`APPLE_NOTES_MCP_BACKUP_DIR` to relocate) |
Purge everything: `rm -rf ~/.mcp-apple-notes ~/.local/share/apple-notes-mcp`
## Troubleshooting
| Symptom | Fix |
|---|---|
| `osascript` errors, or indexing finds 0 notes | Automation permission missing. System Settings → Privacy & Security → Automation → your client → enable **Notes**. |
| `Not authorised to send Apple events` | Same. Quit and reopen the client after granting. |
| First search hangs for a minute | One-time model download. Instant afterwards. |
| Search misses a note you just wrote | Re-run `index-notes`. |
| `command not found: applenotes-mcp` | `~/.local/bin` not on `PATH`, or you used the hyphenated name. |
| Won't start on Linux or Windows | It can't — Apple Notes is macOS-only. |
## Bonus: conversation-tag for Ghostty
[`plugin/`](plugin/) is an optional macOS + [Ghostty](https://ghostty.org) extra:
it tags each terminal tab with a pixel-art sprite and title pulled from today's
Apple Notes todo list, so six parallel Claude sessions stop looking identical.
Built on this server, installed via its own `install.sh` (it writes a GPU shader,
which the plugin system can't do). See [`plugin/README.md`](plugin/README.md).
## Development
```bash
git clone https://github.com/ashishakkumar/apple-notes-mcp && cd apple-notes-mcp
uv venv
uv pip install --group test # pytest + light runtime deps
uv pip install -e . --no-deps # skips torch; the tests stub the embedder
uv run pytest
```
The suite stubs `_run_jxa`, the single boundary to macOS, so it runs on any
platform and covers injection safety, UTF-8 byte caps, ambiguity refusal,
backup-before-mutate ordering, path traversal in backup filenames, and RRF
fusion. CI additionally exercises `mcp` 1.x and 2.x and completes a real stdio
handshake.
## Credits
Python port of [`mcp-apple-notes`](https://github.com/RafalWilinski/mcp-apple-notes)
by Rafal Wilinski, rewritten to close the injection issues in the original and
add write tools, backups, and folder scoping.
MIT — see [LICENSE](LICENSE).
TDQS
Scored across 12 tools
Most tools have clear, distinct purposes (get vs search, update vs append). The only potential confusion is between list-notes (which counts indexed notes) and list-folder-notes (which lists titles in a folder), and between create-note and create-note-in-folder, but the descriptions clarify these differences.
All tool names follow a consistent kebab-case verb-noun pattern (e.g., list-notes, get-note, update-note). Even the multi-word create-note-in-folder follows the same readable convention, making the set highly predictable.
With 12 tools, the server is well-scoped for Apple Notes management. It covers listing, indexing, searching, creating, updating, appending, backing up, and folder operations without feeling bloated or sparse.
The server provides strong read, create, and update capabilities, but lacks a delete-note tool, which is an obvious gap for a notes management server. Additionally, there is no way to list all notes across folders (only count via list-notes or list by folder), limiting full inventory management.