{
  "markdown": "# MCP Apple Notes\n\n![MCP Apple Notes](./images/logo.png)\n\n**mcp-apple-notes is an MCP server for semantic search and connection-discovery across your own Apple Notes — hybrid search, Swanson-ABC bridges, entity threads, and cited synthesis over everything you've written.**\n\nIt traverses your accumulated mass of notes: semantic + BM25 hybrid search, non-obvious bridge connections, entity threads, related-notes, and grounded synthesis across everything you've ever written in Apple Notes. Embeddings, search, BM25, clustering, and bridges run **on-device**; only **synthesis generation** calls an LLM (local via LM Studio/Ollama, or cloud via OpenAI — your choice). Built on the [Model Context Protocol (MCP)](https://www.anthropic.com/news/model-context-protocol).\n\n> Forked from [RafalWilinski/mcp-apple-notes](https://github.com/RafalWilinski/mcp-apple-notes) — this fork reads the SQLite store + protobuf directly and adds bridges, entities, feed, and synthesis.\n\n### Install as a Claude Code plugin\n\n```\n/plugin marketplace add connerkward/ckw-skills\n/plugin install apple-notes@connerkward\n```\n\nThis registers the MCP server and bundles the `apple-notes-search` agent skill. Or install the MCP server manually — see [Installation](#installation) below (requires macOS + bun + Full Disk Access).\n\n> **Other MCP Notes servers break at scale.** They use JXA (AppleScript automation) to read notes one-by-one — fine for 50 notes, unusable at 500+. At 1,800 notes, JXA takes ~49 minutes just to fetch content. On macOS Sequoia it's worse: Apple silently denies Automation permission to processes without a bundle ID, so JXA-based servers fail entirely. This fork reads the SQLite database directly, decodes the protobuf blobs for real note text, and indexes 1,800 notes in under 5 seconds.\n\n![MCP Apple Notes](./images/demo.png)\n\n## Comparison\n\n| Feature | **This fork** | [RafalWilinski](https://github.com/RafalWilinski/mcp-apple-notes) (base) | [disco-trooper](https://github.com/disco-trooper/apple-notes-mcp) | [sirmews](https://github.com/sirmews/apple-notes-mcp) | [dhravya](https://github.com/Dhravya/apple-mcp) |\n|---|---|---|---|---|---|\n| Notes access | SQLite (direct) | JXA | JXA | SQLite | JXA |\n| Fetch 1800 notes | **~430ms** | ~49 min (est.) | ~49 min (est.) | fast | slow |\n| Sequoia compatible | ✅ | ⚠️ JXA denied | ⚠️ JXA denied | ✅ | ⚠️ |\n| Content quality | **Protobuf decoded** | Raw HTML | Raw HTML | Partial | Raw |\n| Semantic search | ✅ | ✅ | ✅ | ❌ | ❌ |\n| Auto re-index on search | ✅ | ❌ | ❌ | ❌ | ❌ |\n| Folder-aware search | ✅ Full path | ❌ | ✅ | ✅ | ✅ |\n| Note chunking | ✅ 1500 chars | ❌ | ✅ | ❌ | ❌ |\n| Re-ranking (RRF × title × recency) | ✅ | RRF only | RRF only | ❌ | ❌ |\n| Non-blocking index UI | ✅ Live progress | ❌ | ❌ | ❌ | ❌ |\n| Incremental indexing | ✅ | ❌ | ✅ | ❌ | ❌ |\n| Update existing notes | ✅ | ❌ | ✅ | ❌ | ❌ |\n| Tags + wikilinks | ✅ | ❌ | ❌ | ❌ | ❌ |\n| Related notes | ✅ Multi-signal | ❌ | ❌ | ❌ | ❌ |\n\n> JXA on macOS Sequoia: processes without a bundle ID are silently auto-denied Automation permission. This fork reads SQLite directly.\n\n## Features\n\n- 🔍 Semantic search via [`all-MiniLM-L6-v2`](https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2) on-device embeddings\n- 📝 BM25 full-text search combined via Reciprocal Rank Fusion\n- 🧠 Re-ranking: `RRF × title_boost × recency_factor`\n- 📂 Full folder hierarchy — filter by any path segment\n- 🕐 Auto re-index: every search runs ~1ms change detection and, if notes changed, kicks ONE background index job (single-flight) — search itself never blocks on indexing\n- ✂️ 1500-char chunking for long notes\n- 🍎 Direct SQLite + protobuf decode — real note text, not garbled HTML\n- 🏃‍♂️ On-device — embeddings, search, BM25, clustering, and bridges run locally with no API keys; only optional **synthesis generation** calls an LLM (local LM Studio/Ollama, or cloud OpenAI — your choice)\n\n## Local web app — not just search, but connections & synthesis\n\nRun the server directly and open the browser UI — no MCP client needed:\n\n```bash\nbun index.ts            # → http://localhost:3741/   (also serves /mcp)\n```\n\nSix modes, building from retrieval toward sensemaking:\n\n- **Feed** (default) — one ranked, evidence-first stream mixing three connection kinds: **bridges** (A ↔ C via B, with the connecting phrase mined from B as plain-text evidence), **abstraction pairs** (two notes whose LLM-consolidated abstractions from `~/.mcp-apple-notes/consolidated.jsonl` make the same underlying move), and **entity threads** (\"X notes across Y folders mention Z\", from the entity graph). Every item shows its evidence in plain text. 👍/👎 on any item tunes the ranking: a tiny online logistic regression (pure TS, no deps) takes a few SGD steps per vote; votes append to `~/.mcp-apple-notes/votes.jsonl` (with the item's feature vector) and replay on boot, so the ranking survives restarts. Diversity constraints: ≤2 consecutive items of one kind, no note more than twice per page of 20; \"show more\" paginates. Endpoints: `GET /api/feed?offset=&limit=`, `POST /api/vote` `{id, vote: 1|-1}`.\n- **Search** — the hybrid semantic + BM25 search, in a paper-themed UI with query highlighting and folder filter.\n- **Map** — a topic map of every note: spherical k-means clusters (TF-IDF labels) over the embeddings, projected to 2D with PCA. Hover a note for the **neighbor-lens** — lines to its nearest-by-meaning notes, which often cross clusters (the connection-finding payload). Endpoint: `GET /api/clusters?k=`.\n- **Synthesize** — *\"what do I think about X across everything I've written?\"* Query-expansion → relevance-gated retrieval (no recency bias) → MMR diversification → an LLM writes a grounded answer with inline `[n]` citations back to the source notes; provenance is post-checked. Endpoint: `GET /api/synthesize?q=`.\n- **Bridges** — Swanson-ABC discovery over your own notes: pairs (A, C) that are **not** directly similar but are both strongly similar to a shared bridge note B — connections the corpus only makes through an intermediary. Pure arithmetic over the embeddings (score = sim(A,B)·sim(B,C)·(1−sim(A,C)); cross-folder, no shared tags/wikilinks; thresholds auto-relax; hub-penalized selection so no note dominates the list). No LLM, $0. Endpoint: `GET /api/bridges?limit=40`.\n- **Entities** — *\"where else do I talk about Mercedes?\"* Ranked entity chips (people, orgs, concepts) → the notes that mention them, by mention weight. Reads an optional sqlite graph db (see below). Endpoints: `GET /api/entities?q=&limit=`, `GET /api/entity-notes?entity=`.\n\n### Precomputed disk caches (instant bridges & map)\n\nBridge mining (~2 min of embedding) and clustering are computed once and persisted, keyed by a corpus fingerprint (note count + max modification date):\n\n- `~/.mcp-apple-notes/bridges_cache.json` — the full mined bridge pool\n- `~/.mcp-apple-notes/clusters_cache.json` — cluster output per `k`\n\nFresh cache → served from disk instantly (<300ms). Stale (notes changed) → the stale copy is served instantly with a \"recomputing…\" pill in the UI while ONE background job (single-flight) refreshes it. Absent → computed inline once.\n\n### Enabling synthesis (needs an LLM)\n\nEmbeddings/search/clustering/bridges/entities are fully local. Only **synthesis generation** needs an LLM. Point at a local OpenAI-compatible server to keep notes private:\n\n```bash\n# LM Studio / Ollama (zero API cost, notes stay local):\nSYNTH_BASE_URL=http://localhost:1234/v1 SYNTH_MODEL=<loaded-model> OPENAI_API_KEY=local bun index.ts\n# …or real OpenAI: set a funded OPENAI_API_KEY (defaults to gpt-4o-mini).\n```\n\n### The entity graph (optional)\n\nThe entity layer reads `~/.mcp-apple-notes/layered_graph.db` (override with `LAYERED_DB`) — a plain sqlite file produced by the companion [`exp-notes-indexing`](https://github.com/connerkward/exp-notes-indexing) benchmark harness (`layered_graph.py`). No extra dependencies (bun ships `bun:sqlite`); if the file is absent the entity tools and tab simply report how to generate it.\n\n## Installation\n\n1. Clone and install:\n\n```bash\ngit clone https://github.com/connerkward/mcp-apple-notes\ncd mcp-apple-notes\nbun install\n```\n\n2. Add **bun** (`~/.bun/bin/bun`) to **Full Disk Access** in System Settings.\n\n3. Add to `claude_desktop_config.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"apple-notes\": {\n      \"command\": \"/Users/<YOUR_USER_NAME>/.bun/bin/bun\",\n      \"args\": [\"/Users/<YOUR_USER_NAME>/mcp-apple-notes/index.ts\", \"--stdio\"]\n    }\n  }\n}\n```\n\n4. Restart Claude Desktop and ask: *\"Index my Apple Notes\"*.\n\n### Claude Code / CLI registration\n\n```json\n// .mcp.json (project) or `claude mcp add apple-notes -- bun /path/to/mcp-apple-notes/index.ts --stdio`\n{\n  \"mcpServers\": {\n    \"apple-notes\": {\n      \"command\": \"bun\",\n      \"args\": [\"/path/to/mcp-apple-notes/index.ts\", \"--stdio\"]\n    }\n  }\n}\n```\n\nThe same bridges/entities tools power the web UI tabs at the local app (`bun index.ts` → http://localhost:3741/); the entity graph db is optional, generated by the `exp-notes-indexing` benchmark harness.\n\n## Tools\n\n| Tool | Description |\n|------|-------------|\n| `index-notes` | Background indexing with live progress UI |\n| `search-notes` | Semantic + FTS search; optional `folder`, `modifiedAfter`, `modifiedBefore` |\n| `find-notes` | Exact substring search (like Apple Notes built-in); optional `folder`, date range |\n| `get-note` | Full note by title; fuzzy fallback on no exact match |\n| `list-notes` | Notes sorted by recency; optional `folder`, date range, `limit` |\n| `list-folders` | All folders with note counts |\n| `list-tags` | All `#hashtags` across notes, sorted by frequency |\n| `search-by-tag` | Notes containing a specific hashtag |\n| `related-notes` | Related notes via shared tags, `[[wikilinks]]`, and vector similarity |\n| `bridge-notes` | Swanson-ABC bridges: non-similar note pairs connected via a shared intermediary; optional `folder`, `limit` |\n| `feed` | The ranked evidence-first connection feed (bridges + abstraction pairs + entity threads) as JSON; optional `limit` |\n| `entity-notes` | Notes mentioning an entity (e.g. \"Mercedes\"), by mention weight — needs the optional entity graph db |\n| `list-entities` | Entities ranked by mention count; optional substring `query`, `limit` |\n| `get-tables` | Extract pipe/tab-separated tables from a note |\n| `create-note` | Create a note |\n| `update-note` | Edit an existing note |\n| `check-changes` | Check if notes changed since last index (without triggering re-index) |\n| `index-health` | Sync status, last indexed time, note count |\n\n## Search & Ranking\n\nEvery search detects changes (~1ms). If notes changed, ONE background incremental index job is kicked (single-flight — duplicate triggers are dropped) and the search returns immediately from the current index; results catch up when the job lands. The last-indexed watermark persists to `~/.mcp-apple-notes/index_state.json` across restarts. Change detection compares the per-title *set* of modification dates (Apple Notes titles are not unique — \"TODO\" ×10 — and a title→single-date map can never converge for duplicates).\n\n```\nscore = RRF(vector, BM25) × title_boost × recency_factor\n```\n\nTemporal queries (`recent`, `latest`, `today`) automatically shift to a 1-day recency half-life at 70% weight. Normal queries use 90-day half-life at 10% so relevance stays primary.\n\n## Benchmarks\n\n1,806 notes, Apple Silicon:\n\n| Approach | Time | Body included |\n|---|---|---|\n| JXA metadata only | 4,463ms | No |\n| JXA with body | ~49 min | Yes |\n| **SQLite direct** | **430ms** | **Yes** |\n\n## Troubleshooting\n\n```bash\ntail -n 50 -f ~/Library/Logs/Claude/mcp-server-apple-notes.log\n```\n\nPermissions error → ensure **bun** (`~/.bun/bin/bun`) has Full Disk Access in System Settings → Privacy & Security.\n\n---\n\n🧭 **[ckw-skills](https://github.com/connerkward/ckw-skills)** — part of Conner K. Ward's collection of Claude Code skills & MCP servers.\n",
  "bytes": 11906,
  "sha": "d5ec42a657f9095c8505cfffe9d36d5add9298fe5d1b8333560e63fbb2f04c3f",
  "repo_slug": "connerkward/mcp-apple-notes",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_connerkward_mcp_apple_notes_2c8beb8c/readme"
}