apple-notes-mcp 2.9.21 → 2.9.22

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 CHANGED
@@ -1,6 +1,8 @@
1
1
  # Apple Notes MCP Server
2
2
 
3
- A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that enables AI assistants like Claude to read, create, search, and manage notes in Apple Notes on macOS.
3
+ A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that lets Claude (Claude Code and Claude Desktop), Codex, and other MCP clients read, search, create, edit, organize, and export notes in Apple Notes on macOS. It runs locally on your Mac and talks to Notes through AppleScript, Apple Shortcuts, and read-only access to the Notes database.
4
+
5
+ Beyond creating and editing notes, it manages folders and accounts, native tags, checklists, tables, pinned notes, links, and attachments. It can also export notes as Markdown, HTML, or JSON, read audio transcripts and drawings, and run a query language across your whole library.
4
6
 
5
7
  [![npm version](https://img.shields.io/npm/v/apple-notes-mcp)](https://www.npmjs.com/package/apple-notes-mcp)
6
8
  [![npm downloads](https://img.shields.io/npm/dm/apple-notes-mcp)](https://www.npmjs.com/package/apple-notes-mcp)
@@ -15,6 +17,13 @@ A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that e
15
17
  <img src="https://raw.githubusercontent.com/sweetrb/apple-notes-mcp/main/codex/assets/screenshot.png" alt="Apple Notes MCP — create, search, and organize Apple Notes from Codex, Claude, and other AI assistants" width="680">
16
18
  </p>
17
19
 
20
+ ## Contents
21
+
22
+ - [What is This?](#what-is-this) · [Quick Start](#quick-start) · [Requirements](#requirements) · [Features](#features)
23
+ - [Tool Reference](#tool-reference): [notes](#note-operations), [folders](#folder-operations), [accounts](#account-operations), [batch](#batch-operations), [export, attachments, and media](#export-operations), [diagnostics](#diagnostics), [native background operations](#native-background-operations), [private helper](#private-helper-opt-in-unsupported-apple-api)
24
+ - [Usage Patterns](#usage-patterns) · [Installation Options](#installation-options) · [Configuration](#configuration) · [Full Disk Access](#full-disk-access) · [Public native helper](#public-native-helper)
25
+ - [Security and Privacy](#security-and-privacy) · [Known Limitations](#known-limitations) · [Troubleshooting](#troubleshooting) · [Recurring macOS permission prompts](#recurring-macos-permission-prompts) · [Development](#development)
26
+
18
27
  ## What is This?
19
28
 
20
29
  This server acts as a bridge between AI assistants and Apple Notes. Once configured, you can ask Claude (or any MCP-compatible AI) to:
@@ -25,7 +34,7 @@ This server acts as a bridge between AI assistants and Apple Notes. Once configu
25
34
  - "Move my draft notes to the Archive folder"
26
35
  - "What notes do I have in my Work folder?"
27
36
 
28
- The AI assistant communicates with this server, which then uses AppleScript to interact with the Notes app on your Mac. All data stays local on your machine.
37
+ The AI assistant communicates with this server, which then uses AppleScript to interact with the Notes app on your Mac. Some features also use packaged Apple Shortcuts (native tags, checklists, tables, pinning) or read the Notes database read-only (queries, checklist state, transcripts). The server itself makes no network requests; what your MCP client does with the results is up to that client.
29
38
 
30
39
  ## Quick Start
31
40
 
@@ -56,7 +65,7 @@ Install as a Claude Code plugin for automatic configuration and enhanced AI beha
56
65
 
57
66
  This method also installs a **skill** that teaches Claude when and how to use Apple Notes effectively.
58
67
 
59
- On the first tool call, macOS shows an Automation permission prompt ("Claude" wants access to control "Notes") — click **OK**. Optionally, grant **Full Disk Access** (under Claude Desktop, to the Node binary that runs the server; from a terminal, to the terminal app) to enable the database-backed tools (`get-checklist-state`, `get-note-metadata`, `get-audio-transcripts`, `list-special-notes`, `list-native-tags`, `list-recent-notes`, `list-folder-tree`, `get-note-link`, checklist annotations in `get-note-markdown`, and full `get-sync-status` detail); see the [Full Disk Access Setup Guide](https://github.com/sweetrb/apple-notes-mcp/blob/main/docs/FULL-DISK-ACCESS.md). The rest of the server is pure AppleScript and works without it.
68
+ On the first tool call, macOS shows an Automation permission prompt ("Claude" wants access to control "Notes") — click **OK**. Optionally, grant **Full Disk Access** (under Claude Desktop, to the Node binary that runs the server; from a terminal, to the terminal app) to enable the database-backed tools, such as `query-notes`, `get-checklist-state`, `get-note-tables`, `get-audio-transcripts`, `list-native-tags`, and `list-recent-notes` (the full list is under [Full Disk Access](#full-disk-access)); see the [Full Disk Access Setup Guide](https://github.com/sweetrb/apple-notes-mcp/blob/main/docs/FULL-DISK-ACCESS.md). The rest of the server is pure AppleScript and works without it.
60
69
 
61
70
  Native tag, checklist, table, pin, and rich append operations use two packaged
62
71
  Apple Shortcuts. A third, `Apple Notes MCP - Create Markdown Note`, is optional:
@@ -148,6 +157,10 @@ npm install -g apple-notes-mcp
148
157
 
149
158
  On first use, macOS will ask for permission to automate Notes.app. Click "OK" to allow.
150
159
 
160
+ ### Other MCP clients
161
+
162
+ The server is a standard MCP server over stdio, so any client that can launch a local stdio server can run it with the same command the hosts above use: `npx -y apple-notes-mcp`. Most clients take it in an `mcpServers` entry shaped like the Claude Desktop example. If your client cannot pass environment variables, use the [configuration file](#configuration-file-when-the-host-strips-env).
163
+
151
164
  ## Requirements
152
165
 
153
166
  - **macOS** - Apple Notes and AppleScript are macOS-only
@@ -158,24 +171,30 @@ On first use, macOS will ask for permission to automate Notes.app. Click "OK" to
158
171
 
159
172
  | Feature | Description |
160
173
  |---------|-------------|
161
- | **Create Notes** | Create notes with titles, content, and optional folder/account targeting |
174
+ | **Create Notes** | Create notes from plaintext, HTML, or Markdown, with optional folder/account targeting (Markdown creation uses an optional Shortcut on macOS 26 or later) |
162
175
  | **Search Notes** | Find notes by title or search within note content |
163
176
  | **Query Language** | `query-notes` combines text, folder, account, tag, attachment, checklist, flag, word-count, and date conditions with AND/OR/NOT, read from the Notes database (requires Full Disk Access) |
164
- | **Read Notes** | Retrieve note content and metadata |
165
- | **Update Notes** | Modify existing notes (title and/or content) |
177
+ | **Read Notes** | Retrieve note content as HTML, plain text, or Markdown, plus metadata that AppleScript does not expose (pinned, Quick Note, locked, Recently Deleted) |
178
+ | **Note Structure** | Decode a note into typed paragraph blocks, list its paragraphs and links, and get a direct link to one paragraph (requires Full Disk Access) |
179
+ | **Update Notes** | Replace, append to, or prepend to an existing note, guarded by a content hash so a note that changed since it was read is never overwritten |
166
180
  | **Delete Notes** | Remove notes (moves to Recently Deleted) |
167
181
  | **Move Notes** | Organize notes into folders (supports nested paths) |
168
- | **Folder Management** | Create, list, and delete folders with full hierarchical path support |
182
+ | **Folder Management** | Create, list, rename, and delete folders with full hierarchical path support; read the folder tree with note counts and Smart Folder rules |
169
183
  | **Multi-Account** | Work with iCloud, Gmail, Exchange, or any configured account, including account IDs and default folders |
170
184
  | **Batch Operations** | Delete or move multiple notes at once |
171
- | **Checklist State** | Read checklist done/undone state directly from the Notes database (requires Full Disk Access) |
172
- | **Audio Transcripts** | Read the transcripts and summaries Notes stored for audio recordings (requires Full Disk Access) |
173
- | **Export** | Export all notes as JSON or get individual notes as Markdown |
174
- | **Attachments** | List attachments with their on-disk asset and preview paths, find a note's lead image, save or batch-export them to disk, or fetch their bytes as base64 |
175
- | **Notes.app UI State** | Reveal a note in Notes.app or read the current Notes.app selection |
185
+ | **Native Tags** | List, add, remove, and replace real Notes tags (not just `#hashtag` text) |
186
+ | **Checklists** | Read checklist done/undone state from the Notes database, and append real checklist items through a Shortcut |
187
+ | **Tables** | Read native tables as Markdown and JSON, and append new native tables |
188
+ | **Pinning** | Read pinned state and pin or unpin a note |
189
+ | **Links** | Get a note's `notes://` deep link, insert web, mail, or note-to-note links, and list the links in a note or folder |
190
+ | **Export** | Export notes as paginated JSON, or render a note or folder as Markdown (with reusable templates) or standalone HTML |
191
+ | **Attachments** | Add files to notes (from a path or the pasteboard), list attachments with their on-disk paths, find a note's lead image, save or batch-export them, or fetch their bytes as base64 |
192
+ | **Audio and Drawings** | Read the transcripts Notes stored for recordings, transcribe audio on-device, and decode drawings to strokes and SVG |
193
+ | **Incremental Sync** | `list-recent-notes` pages through notes by modification time with an exact cursor |
194
+ | **Notes.app UI State** | Reveal a note, folder, account, or attachment in Notes.app, or read the current Notes.app selection |
176
195
  | **Sync Awareness** | Detect iCloud sync in progress, warn about incomplete results |
177
196
  | **Collaboration** | Detect shared notes, warn before modifying |
178
- | **Diagnostics** | `health-check` plus a richer `doctor` (reachability, automation permission, accounts, Full Disk Access), sync status, and statistics |
197
+ | **Diagnostics** | `health-check` plus a richer `doctor` (reachability, automation permission, accounts, Full Disk Access), sync status, statistics, and `get-capabilities` for the Shortcut bridges |
179
198
 
180
199
  Read/list/get tools also return **structured JSON** (`structuredContent`) alongside the text, so agents can consume results without parsing prose.
181
200
 
@@ -193,8 +212,8 @@ reading Notes' own database instead; the rest genuinely cannot be supported. See
193
212
  **[docs/APPLESCRIPT-LIMITATIONS.md](https://github.com/sweetrb/apple-notes-mcp/blob/main/docs/APPLESCRIPT-LIMITATIONS.md)**
194
213
  for the investigation and verification behind each:
195
214
 
196
- - **Pinned notes** — Notes has no scriptable `pinned` property via AppleScript. Pin state can now be **read** with the BETA `get-note-metadata` tool (from the NoteStore database), but it still cannot be **set** programmatically.
197
- - **Note-to-note links** — AppleScript exposes no link property or link element, so link *relationships* between notes cannot be read, and a link cannot be inserted into a note body. A shareable `notes://showNote?identifier=<uuid>` deep link **is** available via [`get-note-link`](#get-note-link).
215
+ - **Pinned notes** — Notes has no scriptable `pinned` property via AppleScript. Pin state is **read** from the NoteStore database by the BETA `get-note-metadata` tool and `list-special-notes`, and **set** through the Background Operations Shortcut by [`set-note-pinned`](#set-note-pinned).
216
+ - **Note-to-note links** — AppleScript exposes no link property or link element. Links are instead read from the NoteStore database by [`list-note-links`](#list-note-links), and inserted by [`insert-note-link`](#insert-note-link) (through a Shortcut) or [`insert-link`](#insert-link). A shareable `notes://showNote?identifier=<uuid>` deep link is available via [`get-note-link`](#get-note-link).
198
217
 
199
218
  ---
200
219
 
@@ -601,6 +620,10 @@ stable row and column identifiers. `tableCellsComplete` is false when Notes
601
620
  metadata cannot be decoded completely. This tool is read-only and requires Full
602
621
  Disk Access.
603
622
 
623
+ | Parameter | Type | Required | Description |
624
+ |-----------|------|----------|-------------|
625
+ | `id` | string | Yes | Exact note ID, in any [identifier form](#identifier-forms) |
626
+
604
627
  ---
605
628
 
606
629
  #### `get-note-tables`
@@ -861,6 +884,13 @@ Shortcut runs. It refuses ambiguous title-and-scope matches and never retries an
861
884
  uncertain write. Install the signed workflow as described in
862
885
  [`shortcuts/README.md`](shortcuts/README.md).
863
886
 
887
+ | Parameter | Type | Required | Description |
888
+ |-----------|------|----------|-------------|
889
+ | `id` | string | Yes | Exact note ID, in any [identifier form](#identifier-forms) |
890
+ | `expectedContentHash` | string | Yes | `contentHash` from a fresh read of the note |
891
+ | `scopeText` | string | Yes | Distinctive phrase already in the note, 12–500 characters |
892
+ | `tags` | string[] | Yes | 1–100 tag names to add |
893
+
864
894
  ---
865
895
 
866
896
  #### `get-note-plaintext`
@@ -948,6 +978,7 @@ Updates an existing note's content and/or title.
948
978
  | `format` | string | No | Content format: `"plaintext"` (default) or `"html"`. When `"html"`, content replaces the entire note body as raw HTML and `newTitle` is ignored (the first HTML element serves as the title) |
949
979
  | `allowLinkChanges` | boolean | No | Set to `true` only when intentionally changing or removing existing links |
950
980
  | `ifFolderId`, `ifAncestorFolderId`, `forbiddenAncestorFolderIds` | string, string, string[] | No | Folder preconditions; see [Folder scope guards](#folder-scope-guards) |
981
+ | `timeoutSeconds` | number | No | Per-call timeout, 1–120 seconds, as for [`create-note`](#create-note). A timed-out write is uncertain: read the note by id before any retry |
951
982
 
952
983
  Title-only updates are rejected because Apple Notes titles are not unique.
953
984
 
@@ -993,6 +1024,7 @@ Deletes a note (moves to Recently Deleted in Notes.app).
993
1024
  | `id` | string | Yes | Exact CoreData note ID returned by a read or search |
994
1025
  | `expectedContentHash` | string | Yes | `contentHash` from the exact note version being deleted |
995
1026
  | `ifFolderId`, `ifAncestorFolderId`, `forbiddenAncestorFolderIds` | string, string, string[] | No | Folder preconditions; see [Folder scope guards](#folder-scope-guards) |
1027
+ | `timeoutSeconds` | number | No | Per-call timeout, 1–120 seconds, as for [`create-note`](#create-note). A timed-out write is uncertain: read the note by id before any retry |
996
1028
  | `guardNoteId` | string | No | A second note (usually a verified copy) that must still be intact; needs Full Disk Access |
997
1029
  | `expectedGuardContentHash` | string | With `guardNoteId` | `contentHash` of the guard note from `get-note-content` |
998
1030
  | `requireActiveNoteId` | string | No | A second note that must still exist, be unlocked, stay outside Recently Deleted, and not be a Quick Note; its content is not fingerprinted. Needs Full Disk Access |
@@ -1056,7 +1088,9 @@ A smart folder is never a destination: it only gathers notes by its rules, and N
1056
1088
  |-----------|------|----------|-------------|
1057
1089
  | `id` | string | Yes | Exact CoreData note ID returned by a read or search |
1058
1090
  | `folder` | string | Yes | Destination folder name or nested path (e.g., `"Work/Clients"`) |
1091
+ | `account` | string | No | Account whose `folder` is the destination (default: the default account) |
1059
1092
  | `ifFolderId`, `ifAncestorFolderId`, `forbiddenAncestorFolderIds` | string, string, string[] | No | Folder preconditions; see [Folder scope guards](#folder-scope-guards) |
1093
+ | `timeoutSeconds` | number | No | Per-call timeout, 1–120 seconds, as for [`create-note`](#create-note). A timed-out write is uncertain: read the note by id before any retry |
1060
1094
 
1061
1095
  Title-only moves are rejected.
1062
1096
 
@@ -1114,7 +1148,9 @@ Appends or prepends content to an existing note without replacing it. Always rea
1114
1148
  | `position` | string | No | `"after"` (default) appends to the end; `"before"` inserts directly below the note's title line, so the title stays first |
1115
1149
  | `separator` | string | No | String placed between existing content and new content (default: two newlines → `<div><br></div>` in HTML) |
1116
1150
  | `format` | string | No | Format of the content being appended: `"plaintext"` (default) or `"html"` |
1151
+ | `scopeText` | string | Native-object notes only | Unique existing phrase, as for [`append-native`](#append-native) |
1117
1152
  | `ifFolderId`, `ifAncestorFolderId`, `forbiddenAncestorFolderIds` | string, string, string[] | No | Folder preconditions; see [Folder scope guards](#folder-scope-guards) |
1153
+ | `timeoutSeconds` | number | No | Per-call timeout, 1–120 seconds, as for [`create-note`](#create-note). A timed-out write is uncertain: read the note by id before any retry |
1118
1154
 
1119
1155
  Title-only appends are rejected.
1120
1156
 
@@ -2355,6 +2391,15 @@ distinctive existing `scopeText` so the Shortcut and server can independently
2355
2391
  resolve the same note. Creating a note from Markdown is
2356
2392
  [`create-note`](#create-note) with `format: "markdown"`.
2357
2393
 
2394
+ Every tool below that edits one note takes these parameters, plus the ones
2395
+ listed under the tool itself:
2396
+
2397
+ | Parameter | Type | Required | Description |
2398
+ |-----------|------|----------|-------------|
2399
+ | `id` | string | Yes | Exact note ID: x-coredata id, Notes UUID, or numeric key (see [Identifier forms](#identifier-forms)) |
2400
+ | `expectedContentHash` | string | Yes | `contentHash` (`sha256:…`) from a fresh read of the note |
2401
+ | `scopeText` | string | Yes | Distinctive phrase already in the note, 12–500 characters, that Notes search can find. Prefer plain words without punctuation, hashtags, or paths |
2402
+
2358
2403
  When the Shortcut refuses a request (its Find Notes step did not return
2359
2404
  exactly one note with that exact title and `scopeText`) or stops with its own
2360
2405
  action error, and the note reads back unchanged, the tool reports the
@@ -2419,11 +2464,20 @@ unapproved bridge is identified rather than guessed at; a timeout also says it
2419
2464
  may be an unanswered first-run consent prompt and names the Shortcut to run once
2420
2465
  in the foreground.
2421
2466
 
2467
+ | Parameter | Type | Required | Description |
2468
+ |-----------|------|----------|-------------|
2469
+ | `content` | string | Yes | Content to append, up to 1,048,576 characters |
2470
+ | `format` | string | No | `"plaintext"` (default), `"html"`, or `"markdown"` |
2471
+
2422
2472
  #### `create-checklist-item`
2423
2473
 
2424
2474
  Appends one real unchecked Notes checklist item and verifies its native identity
2425
2475
  and text.
2426
2476
 
2477
+ | Parameter | Type | Required | Description |
2478
+ |-----------|------|----------|-------------|
2479
+ | `text` | string | Yes | The item's text |
2480
+
2427
2481
  #### `create-checklist-items`
2428
2482
 
2429
2483
  Appends several real unchecked checklist items, 1 to 20, in the order given.
@@ -2463,27 +2517,54 @@ cell. It never substitutes a text table. Omit `rows` for an empty 2 × 2 table,
2463
2517
  the size Notes itself inserts from Format > Table; its four empty cells are
2464
2518
  verified the same way.
2465
2519
 
2520
+ | Parameter | Type | Required | Description |
2521
+ |-----------|------|----------|-------------|
2522
+ | `rows` | string[][] | No | Cell text, row by row: 1–1,000 rows of 1–100 cells, every row the same length. Omit for an empty 2 × 2 table |
2523
+
2466
2524
  #### `set-note-pinned`
2467
2525
 
2468
2526
  Sets an explicit pinned state after checking both the expected current state and
2469
2527
  the note revision. It does not rewrite the body.
2470
2528
 
2529
+ | Parameter | Type | Required | Description |
2530
+ |-----------|------|----------|-------------|
2531
+ | `expectedPinned` | boolean | Yes | The pinned state you last read; the call is refused if it differs |
2532
+ | `pinned` | boolean | Yes | The pinned state to set |
2533
+
2471
2534
  #### `remove-native-tags`
2472
2535
 
2473
2536
  Removes specified tags from one exact note while preserving unrelated tags and
2474
2537
  native objects. It does not delete global tag definitions.
2475
2538
 
2539
+ | Parameter | Type | Required | Description |
2540
+ |-----------|------|----------|-------------|
2541
+ | `tags` | string[] | Yes | 1–100 tag names to remove |
2542
+
2476
2543
  #### `replace-native-tag`
2477
2544
 
2478
2545
  Adds and verifies the new tag before removing the old tag on an explicit list of
2479
2546
  freshly read notes. Stops on the first uncertain result. Smart Folder rules are
2480
2547
  not changed.
2481
2548
 
2549
+ Unlike the other tools in this section, it takes a list of notes instead of one
2550
+ `id`:
2551
+
2552
+ | Parameter | Type | Required | Description |
2553
+ |-----------|------|----------|-------------|
2554
+ | `notes` | object[] | Yes | 1–100 entries, each with the note's `id`, `expectedContentHash`, and `scopeText` |
2555
+ | `oldTag` | string | Yes | Tag to remove after the new one is verified |
2556
+ | `newTag` | string | Yes | Tag to add |
2557
+
2482
2558
  #### `insert-note-link`
2483
2559
 
2484
2560
  Retrieves another note's real deep link and appends it with a static label while
2485
2561
  preserving the target note's native objects.
2486
2562
 
2563
+ | Parameter | Type | Required | Description |
2564
+ |-----------|------|----------|-------------|
2565
+ | `linkedNoteId` | string | Yes | ID of the note to link to, in any [identifier form](#identifier-forms) |
2566
+ | `label` | string | No | Visible link text (default: the linked note's title) |
2567
+
2487
2568
  ### Private helper (opt-in, unsupported Apple API)
2488
2569
 
2489
2570
  An optional, **read-only** native helper reads note state through Notes' own
@@ -2676,8 +2757,14 @@ All configuration is optional — the server works out of the box. Override beha
2676
2757
  | `APPLE_NOTES_MCP_RETRY_DELAY_MS` | `1000` (1 s) | Base delay before the first retry; subsequent retries back off exponentially (1s, 2s, 4s, ...). |
2677
2758
  | `APPLE_NOTES_MCP_ENABLE_PRIVATE` | unset | Set to `1` to allow the opt-in [private helper](#private-helper-opt-in-unsupported-apple-api). Any other value keeps it off. |
2678
2759
  | `APPLE_NOTES_MCP_PRIVATE_HELPER_DIR` | `~/Library/Application Support/apple-notes-mcp/private-helper` | Where `setup --native-helper` installs the helper and its checksum manifest. |
2679
- | `APPLE_NOTES_MCP_PRIVATE_HELPER_TIMEOUT_MS` | `20000` (20 s) | Per-call helper timeout. A timed-out write is indeterminate. |
2760
+ | `APPLE_NOTES_MCP_PRIVATE_HELPER_TIMEOUT_MS` | `20000` (20 s) | Per-call timeout for the private helper. The helper is read-only, so a timeout changes nothing in Notes. |
2680
2761
  | `APPLE_NOTES_MCP_PRIVATE_STORE` | unset | Testing only: points the helper at a **copy** of `NoteStore.sqlite`. The helper refuses a path that resolves to the live store. |
2762
+ | `APPLE_NOTES_MCP_PUBLIC_HELPER_DIR` | `~/Library/Application Support/apple-notes-mcp/public-helper` | Where `setup --public-helper` installs the [public native helper](#public-native-helper) and its checksum manifest. |
2763
+ | `APPLE_NOTES_MCP_PUBLIC_HELPER_TIMEOUT_MS` | `30000` (30 s) | Per-call timeout for the public native helper (`get-note-drawings`, `transcribe-note-audio`). |
2764
+ | `APPLE_NOTES_MCP_TAGS_SHORTCUT` | `Apple Notes MCP - Native Tags` | Name or UUID of the installed Native Tags bridge to run. `setup` still installs and checks the default name. |
2765
+ | `APPLE_NOTES_MCP_BACKGROUND_SHORTCUT` | `Apple Notes MCP - Background Operations v5` | Name or UUID of the installed Background Operations bridge to run. `setup` still installs and checks the default name. |
2766
+ | `APPLE_NOTES_MCP_MARKDOWN_SHORTCUT` | `Apple Notes MCP - Create Markdown Note` | Name or UUID of the installed Create Markdown Note bridge to run. `setup` still installs and checks the default name. |
2767
+ | `APPLE_NOTES_MCP_PASTEBOARD_NAME` | unset | Testing only: makes [`add-attachment-from-pasteboard`](#add-attachment-from-pasteboard) read a private named pasteboard instead of the general clipboard. |
2681
2768
  | `DEBUG` / `VERBOSE` | unset | Set either to enable verbose diagnostic logging to stderr. |
2682
2769
 
2683
2770
  ### Configuration file (when the host strips `env`)
@@ -2704,7 +2791,7 @@ MCP stores no secrets, but as a general rule keep only non-secret config here.
2704
2791
 
2705
2792
  ## Full Disk Access
2706
2793
 
2707
- Several tools read directly from the Apple Notes SQLite database, which lives in a macOS-protected directory. Those tools require **Full Disk Access** for the process running the MCP server: `get-checklist-state`, `get-note-metadata`, `get-note-blocks`, `list-note-paragraphs`, `get-paragraph-link`, `get-note-structure`, `list-note-links`, `export-notes-markdown`, `export-notes-html`, `get-audio-transcripts`, `list-special-notes`, `list-native-tags`, `list-recent-notes`, `list-folder-tree`, `get-note-link`, the checklist annotations in `get-note-markdown`, `list-attachments` with `includePaths` or `firstImage`, `export-attachments`, `list-paper-attachments`, `export-paper-image`, and the database half of `get-sync-status`.
2794
+ Several tools read directly from the Apple Notes SQLite database, which lives in a macOS-protected directory. Those tools require **Full Disk Access** for the process running the MCP server: `query-notes`, `get-native-objects`, `get-note-tables`, `list-smart-folders`, `delete-folder-by-id`, `get-note-drawings`, `transcribe-note-audio`, `native-note-state`, `get-checklist-state`, `get-note-metadata`, `get-note-blocks`, `list-note-paragraphs`, `get-paragraph-link`, `get-note-structure`, `list-note-links`, `export-notes-markdown`, `export-notes-html`, `get-audio-transcripts`, `list-special-notes`, `list-native-tags`, `list-recent-notes`, `list-folder-tree`, `get-note-link`, the checklist annotations in `get-note-markdown`, `list-attachments` with `includePaths` or `firstImage`, `export-attachments`, `list-paper-attachments`, `export-paper-image`, and the database half of `get-sync-status`.
2708
2795
 
2709
2796
  > 📘 **For the full why-and-how walkthrough (which app to grant, verifying with `doctor`, graceful degradation), see the [Full Disk Access Setup Guide](https://github.com/sweetrb/apple-notes-mcp/blob/main/docs/FULL-DISK-ACCESS.md).** The summary below is the quick version.
2710
2797
 
@@ -2724,6 +2811,8 @@ Several tools read directly from the Apple Notes SQLite database, which lives in
2724
2811
 
2725
2812
  Every tool that does not read the Notes database works normally without Full Disk Access — that is the whole AppleScript surface (create, read, search, update, move, delete, folders, accounts, attachments, stats, export). The database-backed tools degrade like this:
2726
2813
  - `get-checklist-state` returns an error explaining that database access is needed
2814
+ - `query-notes` returns the same kind of error; use `search-notes` instead
2815
+ - `delete-folder-by-id` refuses to delete anything (it fails closed)
2727
2816
  - `get-note-metadata` returns the same kind of error — it has no non-database path
2728
2817
  - `list-special-notes` and the `list-native-tags` inventory return the same kind of error
2729
2818
  - `list-recent-notes` and `list-folder-tree` return the same kind of error
@@ -2751,7 +2840,7 @@ The helper never opens the Notes database and never writes under the Notes group
2751
2840
 
2752
2841
  ## Security and Privacy
2753
2842
 
2754
- - **Local only** - All operations happen locally via AppleScript. No data is sent to external servers.
2843
+ - **Local only** - All operations happen locally, through AppleScript, the packaged Shortcuts, read-only reads of the Notes database, and helpers you build on your own Mac. The server makes no network requests, and `transcribe-note-audio` uses on-device speech recognition only. Whatever your MCP client does with the results is governed by that client.
2755
2844
  - **Permission required** - macOS will prompt for automation permission on first use.
2756
2845
  - **Password-protected notes** - Notes with passwords cannot be read or modified via this server.
2757
2846
  - **No credential storage** - The server doesn't store any passwords or authentication tokens.
@@ -2764,15 +2853,15 @@ The helper never opens the Notes database and never writes under the Notes group
2764
2853
  |------------|--------|
2765
2854
  | macOS only | Apple Notes and AppleScript are macOS-specific |
2766
2855
  | Batch ops run per-note | `batch-delete-notes` / `batch-move-notes` apply each note individually rather than as one bulk operation — AppleScript has no bulk equivalent to IMAP's `UID STORE`/`MOVE`. This is deliberate: it preserves per-note success/failure reporting. ([#26](https://github.com/sweetrb/apple-notes-mcp/issues/26)) |
2767
- | Pinned notes are read-only | AppleScript exposes no `pinned` property. Pin state is readable via the BETA `get-note-metadata` tool (NoteStore database, needs Full Disk Access) but cannot be set ([#28](https://github.com/sweetrb/apple-notes-mcp/issues/28)) |
2856
+ | Pinning needs a Shortcut | AppleScript exposes no `pinned` property. Pin state is readable via the BETA `get-note-metadata` tool (NoteStore database, needs Full Disk Access), and [`set-note-pinned`](#set-note-pinned) sets it through the Background Operations Shortcut rather than AppleScript ([#28](https://github.com/sweetrb/apple-notes-mcp/issues/28)) |
2768
2857
  | Limited rich formatting | Use `format: "html"` on create/update for headings, lists, bold, code blocks; some complex formatting may not render |
2769
- | Title matching | Most operations require exact title matches |
2858
+ | Exact IDs for writes | Writes take an exact note ID and a fresh `contentHash`, because Notes titles are not unique. Read tools that accept a `title` need an exact, case-sensitive match |
2770
2859
  | Checklist state | Requires [Full Disk Access](https://github.com/sweetrb/apple-notes-mcp/blob/main/docs/FULL-DISK-ACCESS.md) to read done/undone state from the database |
2771
- | Checklist **creation** | Not supported. AppleScript's `body of note` setter strips `<input type="checkbox">` and ignores any checklist-styling CSS class. Apple Notes stores checklist items as a protobuf paragraph style (`style_type=103`) that AppleScript doesn't expose, and the SQLite database is read-only. See [Creating Checklists](#creating-checklists) below for the workaround. |
2860
+ | Checklist **creation** | Not possible through AppleScript: its `body of note` setter strips `<input type="checkbox">` and ignores any checklist-styling CSS class, because Notes stores checklist items as a protobuf paragraph style (`style_type=103`). The server creates real checklist items through Shortcuts instead; see [Creating Checklists](#creating-checklists). Checking or unchecking an existing item is not supported. |
2772
2861
 
2773
2862
  ### Creating Checklists
2774
2863
 
2775
- **There is no programmatic way to create a true Apple Notes checklist via AppleScript** — and therefore no way via this MCP server. This is an Apple limitation, not a bug.
2864
+ **There is no programmatic way to create a true Apple Notes checklist via AppleScript.** This is an Apple limitation, not a bug. Plaintext and HTML writes therefore cannot produce checklists, so this server creates them through Apple Shortcuts instead (workaround 2 below).
2776
2865
 
2777
2866
  When a note is created or updated via AppleScript:
2778
2867
 
@@ -2787,7 +2876,7 @@ Apple Notes stores checklists as a paragraph style (`style_type=103`) inside a g
2787
2876
  **Workarounds:**
2788
2877
 
2789
2878
  1. **Create the note with bulleted list items, then convert manually in Notes.app.** Select the items and press <kbd>⇧⌘L</kbd> (or **Format → Checklist**). This converts the list in place and the resulting checklist will be readable by `get-checklist-state` and annotated by `get-note-markdown`.
2790
- 2. **Use the Apple Shortcuts app** to script the checklist creation, since Shortcuts can manipulate Notes content at a higher level than AppleScript. This server does that for you in two ways: [`create-checklist-item`](#create-checklist-item) appends one unchecked item to an existing note, and [`create-note`](#create-note) with `format: "markdown"` turns `- [ ]` / `- [x]` lines into checklist items with that done state through Notes' own Markdown importer, (see [Markdown notes](#markdown-notes)).
2879
+ 2. **Use the Apple Shortcuts app** to script the checklist creation, since Shortcuts can manipulate Notes content at a higher level than AppleScript. This server does that for you in two ways: [`create-checklist-item`](#create-checklist-item) and [`create-checklist-items`](#create-checklist-items) append unchecked items to an existing note, and [`create-note`](#create-note) with `format: "markdown"` turns `- [ ]` / `- [x]` lines into checklist items with that done state through Notes' own Markdown importer (see [Markdown notes](#markdown-notes)).
2791
2880
  3. **Read-only checklist support is fully implemented** — once a checklist exists (created manually or by another app), `get-checklist-state` and `get-note-markdown` will read its done/undone state correctly (with Full Disk Access).
2792
2881
 
2793
2882
  If you need to *track* todos programmatically and don't strictly need them rendered as Apple Notes checklist UI, plain markdown-style `- [ ] item` / `- [x] item` lines in a `plaintext` note are a reasonable alternative — they are searchable, human-readable, and can be parsed by downstream tooling.
@@ -19,7 +19,7 @@ shared, body, id, name, plaintext
19
19
 
20
20
  ## Pinned notes (#28)
21
21
 
22
- **Status: not feasible via AppleScript; readable via the NoteStore database.**
22
+ **Status: not feasible via AppleScript; read from the NoteStore database and set through a Shortcut.**
23
23
  The Notes UI lets you pin a note to the top of a folder, but the `note` class
24
24
  has no `pinned` property. Asking for it raises error `-1700`:
25
25
 
@@ -33,7 +33,7 @@ end tell
33
33
  There is no alternative property, element, or command (`pin`, `pinned`,
34
34
  `favorite`, …) in the dictionary. Pinned state lives only in Notes' private
35
35
  Core Data store (`NoteStore.sqlite`), which is not part of the scriptable
36
- surface, and there is no supported way to *set* it at all.
36
+ surface, and AppleScript offers no way to _set_ it.
37
37
 
38
38
  Reading it, however, did turn out to be worth doing. Since 2.5.0 the BETA
39
39
  `get-note-metadata` tool queries `ZISPINNED` on `ZICCLOUDSYNCINGOBJECT` in that
@@ -43,14 +43,17 @@ changes across macOS releases. It requires
43
43
  [Full Disk Access](./FULL-DISK-ACCESS.md) and is marked BETA precisely because
44
44
  the schema is version-dependent.
45
45
 
46
- **Conclusion:** pin state is **readable** (BETA, from the NoteStore database,
47
- Full Disk Access required) but **not settable** — and setting will not be added
48
- while Notes lacks a scriptable property. If a future macOS exposes one, revisit
49
- by re-running the probe above.
46
+ **Conclusion:** pin state is **not scriptable** through AppleScript in either
47
+ direction. It is **readable** (BETA, from the NoteStore database, Full Disk
48
+ Access required) and, since the native background operations were added,
49
+ **settable** through the packaged Background Operations Shortcut by
50
+ `set-note-pinned`, which checks the expected current state and the note
51
+ revision first. If a future macOS exposes a scriptable property, revisit by
52
+ re-running the probe above.
50
53
 
51
54
  ## Note-to-note links (#30)
52
55
 
53
- **Status: link *relationships* are not exposed; a shareable deep link is.**
56
+ **Status: link _relationships_ are not exposed to AppleScript; the server reads them from the database and inserts links by other routes.**
54
57
  Apple Notes lets you insert a link from one note to another in the UI, but
55
58
  AppleScript exposes no property or element for that relationship:
56
59
 
@@ -58,7 +61,7 @@ AppleScript exposes no property or element for that relationship:
58
61
  (undefined). There is no element that enumerates outgoing/incoming links.
59
62
  - Nothing in the dictionary inserts a link into a note's body.
60
63
 
61
- A shareable deep link to a note *is* available, and has been since 2.6.0:
64
+ A shareable deep link to a note _is_ available, and has been since 2.6.0:
62
65
  `get-note-link` returns a `notes://showNote?identifier=<uuid>` URL that opens
63
66
  the note in Notes.app on macOS and iOS.
64
67
 
@@ -84,10 +87,14 @@ It **is** wrapped, as `show-note`, `show-folder`, `show-account`, and
84
87
  something useful on a machine with an active desktop session; to read a note's
85
88
  content, use `get-note-content` / `get-note-markdown` instead.
86
89
 
87
- **Conclusion:** link relationships between notes cannot be read, so a "list
88
- links in this note" feature is not possible, and links cannot be inserted into a
89
- body. To hand a note to a person or another app, use `get-note-link`; to address
90
- a note in a follow-up tool call, use the `id` returned by every read tool.
90
+ **Conclusion:** AppleScript can neither read link relationships between notes
91
+ nor insert a link into a body. The server does both by other routes:
92
+ `list-note-links` reads a note's or folder's links from the NoteStore database
93
+ (Full Disk Access required), `insert-link` appends a web, mail or Notes link,
94
+ and `insert-note-link` appends another note's real deep link through the
95
+ Background Operations Shortcut. To hand a note to a person or another app, use
96
+ `get-note-link`; to address a note in a follow-up tool call, use the `id`
97
+ returned by every read tool.
91
98
 
92
99
  ## Tags / hashtags (#29)
93
100
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "apple-notes-mcp",
3
- "version": "2.9.21",
4
- "description": "MCP server for Apple Notes - create, search, update, and manage notes via Claude and other AI assistants",
3
+ "version": "2.9.22",
4
+ "description": "MCP server for Apple Notes - read, search, create, edit, organize, and export notes on macOS via Claude and other AI assistants",
5
5
  "type": "module",
6
6
  "main": "build/index.js",
7
7
  "types": "build/index.d.ts",
@@ -25,7 +25,16 @@
25
25
  "applescript",
26
26
  "macos",
27
27
  "notes",
28
- "model-context-protocol"
28
+ "model-context-protocol",
29
+ "mcp-server",
30
+ "apple",
31
+ "notes-app",
32
+ "claude-code",
33
+ "claude-desktop",
34
+ "shortcuts",
35
+ "markdown",
36
+ "note-taking",
37
+ "icloud"
29
38
  ],
30
39
  "author": "Rob Sweet <rob@superiortech.io>",
31
40
  "license": "MIT",