apple-notes-mcp 2.6.12 → 2.6.13
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 +43 -31
- package/build/index.js +44 -21
- package/docs/APPLESCRIPT-LIMITATIONS.md +50 -28
- package/docs/FULL-DISK-ACCESS.md +26 -9
- package/docs/STABILITY-PERF-AUDIT-2026-06-19.md +25 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -56,7 +56,7 @@ Install as a Claude Code plugin for automatic configuration and enhanced AI beha
|
|
|
56
56
|
|
|
57
57
|
This method also installs a **skill** that teaches Claude when and how to use Apple Notes effectively.
|
|
58
58
|
|
|
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** to the app that launches the server to enable the checklist-state
|
|
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** to the app that launches the server to enable the database-backed tools (`get-checklist-state`, `get-note-metadata`, `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.
|
|
60
60
|
|
|
61
61
|
### Using the Codex Marketplace
|
|
62
62
|
|
|
@@ -153,14 +153,15 @@ Resources expose read-only context the client can attach without a tool call:
|
|
|
153
153
|
`notes://note/{id}` template (returns the note as Markdown). Prompts package
|
|
154
154
|
common workflows: `find-note`, `weekly-review`, `new-meeting-note`.
|
|
155
155
|
|
|
156
|
-
###
|
|
156
|
+
### AppleScript limitations
|
|
157
157
|
|
|
158
|
-
A few Notes UI features are not exposed to AppleScript
|
|
159
|
-
supported. See
|
|
158
|
+
A few Notes UI features are not exposed to AppleScript. Some are recovered by
|
|
159
|
+
reading Notes' own database instead; the rest genuinely cannot be supported. See
|
|
160
|
+
**[docs/APPLESCRIPT-LIMITATIONS.md](https://github.com/sweetrb/apple-notes-mcp/blob/main/docs/APPLESCRIPT-LIMITATIONS.md)**
|
|
160
161
|
for the investigation and verification behind each:
|
|
161
162
|
|
|
162
163
|
- **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.
|
|
163
|
-
- **Note-to-note links** —
|
|
164
|
+
- **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).
|
|
164
165
|
|
|
165
166
|
---
|
|
166
167
|
|
|
@@ -179,8 +180,8 @@ Creates a new note in Apple Notes.
|
|
|
179
180
|
| `title` | string | Yes | The title of the note. Automatically prepended as `<h1>` — do NOT include the title in `content` |
|
|
180
181
|
| `content` | string | Yes | The body content of the note (do not repeat the title here) |
|
|
181
182
|
| `tags` | string[] | No | Returned-only metadata — **NOT written to Notes.app**. Apple Notes tags can't be set via AppleScript, so values passed here are echoed back in the response but do not appear on the created note. Use inline `#hashtags` in `content` instead (Notes.app turns those into real tags) |
|
|
182
|
-
| `folder` | string | No | Folder to create the note in. Supports nested paths like `"Work/Clients"`. Defaults to account root |
|
|
183
|
-
| `account` | string | No | Account name (defaults to iCloud) |
|
|
183
|
+
| `folder` | string | No | Folder to create the note in. Supports nested paths like `"Work/Clients"`. **The folder must already exist** — create it first with [`create-folder`](#create-folder). Defaults to account root |
|
|
184
|
+
| `account` | string | No | Account name (defaults to iCloud). Must be an account Notes.app already has configured — see [`list-accounts`](#list-accounts) |
|
|
184
185
|
| `format` | string | No | Content format: `"plaintext"` (default) or `"html"`. In both formats, the title is automatically prepended as `<h1>`. In plaintext mode, newlines become `<br>`, tabs become `<br>`, and backslashes are preserved as HTML entities |
|
|
185
186
|
|
|
186
187
|
**Example (tagged with inline hashtags):**
|
|
@@ -288,6 +289,16 @@ Retrieves the full content of a specific note.
|
|
|
288
289
|
from the body. Apple Notes tags are inline hashtags, not a scriptable property;
|
|
289
290
|
see [docs/APPLESCRIPT-LIMITATIONS.md](https://github.com/sweetrb/apple-notes-mcp/blob/main/docs/APPLESCRIPT-LIMITATIONS.md#tags--hashtags-29). Smart Folders are not scriptable.
|
|
290
291
|
|
|
292
|
+
**⚠️ The returned body can be lossy — do not write it back verbatim.** Inline
|
|
293
|
+
base64 images larger than `APPLE_NOTES_MCP_MAX_INLINE_IMAGE_BYTES` (default
|
|
294
|
+
256 KB) are replaced with `[inline image omitted: …]` text placeholders so an
|
|
295
|
+
image-heavy note cannot blow the MCP message limit. `structuredContent` reports
|
|
296
|
+
this as `strippedImages` (count) and `truncated` (boolean). When either is set,
|
|
297
|
+
passing this body to [`update-note`](#update-note) would replace the real images
|
|
298
|
+
with the placeholder text — use [`append-to-note`](#append-to-note) for
|
|
299
|
+
additions, or export the images with `save-attachment` / `fetch-attachment`
|
|
300
|
+
first.
|
|
301
|
+
|
|
291
302
|
---
|
|
292
303
|
|
|
293
304
|
#### `get-note-plaintext`
|
|
@@ -415,7 +426,7 @@ Updates an existing note's content and/or title.
|
|
|
415
426
|
|
|
416
427
|
**Returns:** Confirmation message, or error if note not found.
|
|
417
428
|
|
|
418
|
-
**Note:** `newContent` **replaces the entire note body** — it is not appended. To
|
|
429
|
+
**Note:** `newContent` **replaces the entire note body** — it is not appended. To add to a note, prefer [`append-to-note`](#append-to-note), which does the read-and-concatenate for you and always round-trips the body as HTML. If you do read-modify-write by hand, note that `get-note-content` replaces oversized inline images with text placeholders (see [`get-note-content`](#get-note-content)) — writing that body back bakes the placeholders in.
|
|
419
430
|
|
|
420
431
|
**Attachments:** A full-body replace can drop embedded files, images, scans, PDFs, or audio. When a note may hold attachments, run [`list-attachments`](#list-attachments) first, and either save them with `save-attachment` or build a new note rather than overwriting. See the skill's [Attachment-Safe Updates](https://github.com/sweetrb/apple-notes-mcp/blob/main/skills/apple-notes/SKILL.md#attachment-safe-updates) guidance.
|
|
421
432
|
|
|
@@ -617,11 +628,11 @@ Lists all folders in an account with full hierarchical paths.
|
|
|
617
628
|
|
|
618
629
|
#### `create-folder`
|
|
619
630
|
|
|
620
|
-
Creates a new folder.
|
|
631
|
+
Creates a new folder, including a whole nested hierarchy in one call.
|
|
621
632
|
|
|
622
633
|
| Parameter | Type | Required | Description |
|
|
623
634
|
|-----------|------|----------|-------------|
|
|
624
|
-
| `name` | string | Yes |
|
|
635
|
+
| `name` | string | Yes | Folder name, or a nested path separated by `/` (e.g. `"Retro Tech/PC/CPUs"`). Every intermediate folder is created; segments that already exist are skipped |
|
|
625
636
|
| `account` | string | No | Account to create folder in (defaults to iCloud) |
|
|
626
637
|
|
|
627
638
|
**Example:**
|
|
@@ -631,7 +642,14 @@ Creates a new folder.
|
|
|
631
642
|
}
|
|
632
643
|
```
|
|
633
644
|
|
|
634
|
-
**
|
|
645
|
+
**Example - Create a nested hierarchy:**
|
|
646
|
+
```json
|
|
647
|
+
{
|
|
648
|
+
"name": "Work/Clients/Omnia"
|
|
649
|
+
}
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
**Returns:** Confirmation message. The call is **idempotent** — an already-existing folder (or path segment) is skipped rather than treated as an error, so it is safe to call before every `create-note` that targets a folder.
|
|
635
653
|
|
|
636
654
|
---
|
|
637
655
|
|
|
@@ -718,7 +736,7 @@ Deletes multiple notes at once by ID.
|
|
|
718
736
|
|
|
719
737
|
| Parameter | Type | Required | Description |
|
|
720
738
|
|-----------|------|----------|-------------|
|
|
721
|
-
| `ids` | string[] | Yes | Array of note IDs to delete |
|
|
739
|
+
| `ids` | string[] | Yes | Array of note IDs to delete (max 500 per request) |
|
|
722
740
|
|
|
723
741
|
**Returns:** Summary of successes and failures.
|
|
724
742
|
|
|
@@ -732,8 +750,8 @@ Moves multiple notes to a folder.
|
|
|
732
750
|
|
|
733
751
|
| Parameter | Type | Required | Description |
|
|
734
752
|
|-----------|------|----------|-------------|
|
|
735
|
-
| `ids` | string[] | Yes | Array of note IDs to move |
|
|
736
|
-
| `folder` | string | Yes | Destination folder name or nested path (e.g., `"Work/Clients"`) |
|
|
753
|
+
| `ids` | string[] | Yes | Array of note IDs to move (max 500 per request) |
|
|
754
|
+
| `folder` | string | Yes | Destination folder name or nested path (e.g., `"Work/Clients"`). Must already exist — create it with [`create-folder`](#create-folder) |
|
|
737
755
|
| `account` | string | No | Account containing the folder |
|
|
738
756
|
|
|
739
757
|
**Returns:** Summary of successes and failures.
|
|
@@ -770,7 +788,7 @@ Gets a note's content as Markdown instead of HTML. If the note contains checklis
|
|
|
770
788
|
|
|
771
789
|
Reads checklist done/undone state for a note. This bypasses the AppleScript limitation where `body of note` strips checklist state, by reading directly from the NoteStore SQLite database.
|
|
772
790
|
|
|
773
|
-
**Requires:** Full Disk Access for the MCP host process (see [Full Disk Access Setup](#full-disk-access
|
|
791
|
+
**Requires:** Full Disk Access for the MCP host process (see [Full Disk Access Setup](#full-disk-access)).
|
|
774
792
|
|
|
775
793
|
| Parameter | Type | Required | Description |
|
|
776
794
|
|-----------|------|----------|-------------|
|
|
@@ -798,7 +816,7 @@ Checklist for "Shopping List" (2/4 done):
|
|
|
798
816
|
|
|
799
817
|
Reads note metadata that AppleScript cannot expose, by querying the NoteStore SQLite database directly: pinned state, checklist flags, trash/recovery state, the preview snippet, and the password hint. The available fields vary by macOS version.
|
|
800
818
|
|
|
801
|
-
**Requires:** Full Disk Access for the MCP host process (see [Full Disk Access Setup](#full-disk-access
|
|
819
|
+
**Requires:** Full Disk Access for the MCP host process (see [Full Disk Access Setup](#full-disk-access)).
|
|
802
820
|
|
|
803
821
|
**BETA:** the NoteStore schema changes between macOS releases, so some fields can be absent on older or newer systems. The database is only ever read, never written.
|
|
804
822
|
|
|
@@ -1063,9 +1081,9 @@ MCP stores no secrets, but as a general rule keep only non-secret config here.
|
|
|
1063
1081
|
|
|
1064
1082
|
---
|
|
1065
1083
|
|
|
1066
|
-
## Full Disk Access
|
|
1084
|
+
## Full Disk Access
|
|
1067
1085
|
|
|
1068
|
-
|
|
1086
|
+
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-link`, the checklist annotations in `get-note-markdown`, and the database half of `get-sync-status`.
|
|
1069
1087
|
|
|
1070
1088
|
> 📘 **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.
|
|
1071
1089
|
|
|
@@ -1083,9 +1101,12 @@ The `get-checklist-state` tool and checklist annotations in `get-note-markdown`
|
|
|
1083
1101
|
|
|
1084
1102
|
### Without Full Disk Access
|
|
1085
1103
|
|
|
1086
|
-
|
|
1087
|
-
- `get-checklist-state`
|
|
1088
|
-
- `get-note-
|
|
1104
|
+
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:
|
|
1105
|
+
- `get-checklist-state` returns an error explaining that database access is needed
|
|
1106
|
+
- `get-note-metadata` returns the same kind of error — it has no non-database path
|
|
1107
|
+
- `get-note-link` returns an error on macOS 26+; on macOS 12–15 it still works via the AppleScript `note link` fallback
|
|
1108
|
+
- `get-note-markdown` returns plain list items without `[x]`/`[ ]` annotations (graceful fallback)
|
|
1109
|
+
- `get-sync-status` still answers, but reports no pending uploads and no active sync — treat that as "unknown", not "idle"
|
|
1089
1110
|
|
|
1090
1111
|
---
|
|
1091
1112
|
|
|
@@ -1104,21 +1125,12 @@ All other tools work normally without Full Disk Access. Only checklist state fea
|
|
|
1104
1125
|
|------------|--------|
|
|
1105
1126
|
| macOS only | Apple Notes and AppleScript are macOS-specific |
|
|
1106
1127
|
| 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)) |
|
|
1107
|
-
|
|
|
1128
|
+
| 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)) |
|
|
1108
1129
|
| Limited rich formatting | Use `format: "html"` on create/update for headings, lists, bold, code blocks; some complex formatting may not render |
|
|
1109
1130
|
| Title matching | Most operations require exact title matches |
|
|
1110
1131
|
| 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 |
|
|
1111
1132
|
| 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. |
|
|
1112
1133
|
|
|
1113
|
-
### Roadmap
|
|
1114
|
-
|
|
1115
|
-
A few capabilities are deliberately deferred to a future release, tracked as open issues:
|
|
1116
|
-
|
|
1117
|
-
- **Pinned-note support** ([#28](https://github.com/sweetrb/apple-notes-mcp/issues/28)) — Apple doesn't expose pin status via AppleScript.
|
|
1118
|
-
- **Tags / hashtags** ([#29](https://github.com/sweetrb/apple-notes-mcp/issues/29)).
|
|
1119
|
-
- **Note links** ([#30](https://github.com/sweetrb/apple-notes-mcp/issues/30)).
|
|
1120
|
-
- **Local integration-test suite** ([#31](https://github.com/sweetrb/apple-notes-mcp/issues/31)).
|
|
1121
|
-
|
|
1122
1134
|
### Creating Checklists
|
|
1123
1135
|
|
|
1124
1136
|
**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.
|
package/build/index.js
CHANGED
|
@@ -38669,6 +38669,14 @@ var StdioServerTransport = class {
|
|
|
38669
38669
|
|
|
38670
38670
|
// src/utils/applescript.ts
|
|
38671
38671
|
import { execFileSync } from "child_process";
|
|
38672
|
+
|
|
38673
|
+
// src/utils/docsUrls.ts
|
|
38674
|
+
var FULL_DISK_ACCESS_GUIDE_URL = "https://github.com/sweetrb/apple-notes-mcp/blob/main/docs/FULL-DISK-ACCESS.md";
|
|
38675
|
+
var NODE_RUNTIME_TCC_GUIDE_URL = "https://github.com/sweetrb/apple-notes-mcp/blob/main/docs/NODE-RUNTIME-AND-TCC-PERMISSIONS.md";
|
|
38676
|
+
var AUTOMATION_PERMISSION_GUIDE_URL = "https://github.com/sweetrb/apple-notes-mcp#permission-denied";
|
|
38677
|
+
var AUTOMATION_REMEDIATION = `Grant automation access in System Settings > Privacy & Security > Automation to the app that launches this server (Claude Desktop / Terminal / iTerm2), then fully quit and relaunch it \u2014 run the doctor tool to verify. See: ${AUTOMATION_PERMISSION_GUIDE_URL}`;
|
|
38678
|
+
|
|
38679
|
+
// src/utils/applescript.ts
|
|
38672
38680
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
38673
38681
|
var DEFAULT_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
|
|
38674
38682
|
function envPositiveNumber(name) {
|
|
@@ -38732,7 +38740,7 @@ var ERROR_MAPPINGS = [
|
|
|
38732
38740
|
// Permission errors
|
|
38733
38741
|
{
|
|
38734
38742
|
pattern: /not authorized|not permitted|access.*denied/i,
|
|
38735
|
-
message:
|
|
38743
|
+
message: `Permission denied. ${AUTOMATION_REMEDIATION}`
|
|
38736
38744
|
},
|
|
38737
38745
|
// Application not running
|
|
38738
38746
|
{
|
|
@@ -39000,10 +39008,6 @@ function embeddedMessage(field) {
|
|
|
39000
39008
|
return decodeMessage(bytes);
|
|
39001
39009
|
}
|
|
39002
39010
|
|
|
39003
|
-
// src/utils/docsUrls.ts
|
|
39004
|
-
var FULL_DISK_ACCESS_GUIDE_URL = "https://github.com/sweetrb/apple-notes-mcp/blob/main/docs/FULL-DISK-ACCESS.md";
|
|
39005
|
-
var NODE_RUNTIME_TCC_GUIDE_URL = "https://github.com/sweetrb/apple-notes-mcp/blob/main/docs/NODE-RUNTIME-AND-TCC-PERMISSIONS.md";
|
|
39006
|
-
|
|
39007
39011
|
// src/utils/checklistParser.ts
|
|
39008
39012
|
var CHECKLIST_STYLE_TYPE = 103;
|
|
39009
39013
|
var NOTES_DB_PATH = path.join(
|
|
@@ -40722,7 +40726,7 @@ var AppleNotesManager = class {
|
|
|
40722
40726
|
checks.push({
|
|
40723
40727
|
name: "permissions",
|
|
40724
40728
|
passed: !isPermError,
|
|
40725
|
-
message: isPermError ?
|
|
40729
|
+
message: isPermError ? `AppleScript permissions denied. ${AUTOMATION_REMEDIATION}` : `Permission check returned: ${permCheck.error}`
|
|
40726
40730
|
});
|
|
40727
40731
|
if (isPermError) {
|
|
40728
40732
|
return { healthy: false, checks };
|
|
@@ -41833,7 +41837,7 @@ function strippedImagesWarning(stripped) {
|
|
|
41833
41837
|
|
|
41834
41838
|
\u26A0\uFE0F ${stripped.strippedCount} inline ${plural} (~${formatBytes(
|
|
41835
41839
|
stripped.strippedBytes
|
|
41836
|
-
)} decoded) exceeded the per-image inline cap and ${stripped.strippedCount === 1 ? "was" : "were"} replaced with placeholders so the response stays within MCP message limits. The images are still in the note: use list-attachments with save-attachment or fetch-attachment to export them, or raise APPLE_NOTES_MCP_MAX_INLINE_IMAGE_BYTES.`;
|
|
41840
|
+
)} decoded) exceeded the per-image inline cap and ${stripped.strippedCount === 1 ? "was" : "were"} replaced with placeholders so the response stays within MCP message limits. This body is therefore lossy \u2014 do NOT write it back with update-note, or the real images are replaced by the placeholder text; use append-to-note to add content. The images are still in the note: use list-attachments with save-attachment or fetch-attachment to export them, or raise APPLE_NOTES_MCP_MAX_INLINE_IMAGE_BYTES.`;
|
|
41837
41841
|
}
|
|
41838
41842
|
|
|
41839
41843
|
// src/utils/updateResponseTitle.ts
|
|
@@ -41924,7 +41928,7 @@ function runDoctor(manager) {
|
|
|
41924
41928
|
checks.push({
|
|
41925
41929
|
name: "Full Disk Access",
|
|
41926
41930
|
status: fda ? "ok" : "warn",
|
|
41927
|
-
detail: fda ? "granted \u2014 checklist
|
|
41931
|
+
detail: fda ? "granted \u2014 the Notes database is readable (checklist state, note metadata, note links, sync detail)" : `not granted \u2014 get-checklist-state, get-note-metadata, and the checklist annotations in get-note-markdown won't work; get-note-link fails on macOS 26+ (macOS 12-15 falls back to AppleScript); get-sync-status still answers but cannot see pending uploads. Everything else is pure AppleScript and is unaffected. In System Settings > Privacy & Security > Full Disk Access, grant access to the app that launches this server (Claude Desktop / Terminal / iTerm2), then fully quit and relaunch it and re-run doctor. Setup guide: ${FULL_DISK_ACCESS_GUIDE_URL}`
|
|
41928
41932
|
});
|
|
41929
41933
|
checks.push(checkNodeRuntimeSignature());
|
|
41930
41934
|
const healthy = !checks.some((c) => c.status === "fail");
|
|
@@ -42125,7 +42129,7 @@ var folderNameSchema = {
|
|
|
42125
42129
|
server.registerTool(
|
|
42126
42130
|
"create-note",
|
|
42127
42131
|
{
|
|
42128
|
-
description: "Use when: the user wants to create a brand-new Apple Note.\nReturns: the new note's title and id \u2014 reuse the id for follow-up reads/edits.\nDo not use when: editing an existing note (use update-note).\nNote: the title is prepended as an <h1>; true Apple Notes checklists cannot be created via AppleScript (see the content field).",
|
|
42132
|
+
description: "Use when: the user wants to create a brand-new Apple Note.\nReturns: the new note's title and id \u2014 reuse the id for follow-up reads/edits.\nDo not use when: editing an existing note (use update-note).\nNote: the title is prepended as an <h1>; true Apple Notes checklists cannot be created via AppleScript (see the content field). A 'folder' must already exist \u2014 create-folder first (it is idempotent), since this tool does not create it.",
|
|
42129
42133
|
inputSchema: {
|
|
42130
42134
|
title: external_exports.string().min(1, "Title is required").max(MAX.TITLE),
|
|
42131
42135
|
content: external_exports.string().min(1, "Content is required").max(MAX.CONTENT).describe(
|
|
@@ -42135,8 +42139,12 @@ server.registerTool(
|
|
|
42135
42139
|
tags: external_exports.array(external_exports.string().max(MAX.TAG)).max(MAX.TAGS).optional().describe(
|
|
42136
42140
|
"Returned-only metadata \u2014 NOT written to Notes.app. Apple Notes tags can't be set via AppleScript, so any values passed here are echoed back in the response but do not appear on the created note. Use #hashtags inside the content body instead (Notes.app turns those into real tags)."
|
|
42137
42141
|
),
|
|
42138
|
-
folder: external_exports.string().max(MAX.FOLDER).optional().describe(
|
|
42139
|
-
|
|
42142
|
+
folder: external_exports.string().max(MAX.FOLDER).optional().describe(
|
|
42143
|
+
"Folder to create the note in (supports nested paths like 'Work/Clients'). The folder must already exist \u2014 this tool does not create it; call create-folder first, which is idempotent and creates intermediate segments."
|
|
42144
|
+
),
|
|
42145
|
+
account: external_exports.string().max(MAX.ACCOUNT).optional().describe(
|
|
42146
|
+
"Account name (defaults to iCloud). Must be an account Notes.app already has configured \u2014 see list-accounts."
|
|
42147
|
+
)
|
|
42140
42148
|
},
|
|
42141
42149
|
outputSchema: {
|
|
42142
42150
|
ok: external_exports.boolean().optional(),
|
|
@@ -42149,8 +42157,9 @@ server.registerTool(
|
|
|
42149
42157
|
withErrorHandling(({ title, content, format = "plaintext", tags = [], folder, account }) => {
|
|
42150
42158
|
const note = notesManager.createNote(title, content, tags, folder, account, format);
|
|
42151
42159
|
if (!note) {
|
|
42160
|
+
const target = folder ? ` Most often the folder "${folder}" does not exist: run list-folders to check, then create-folder to create it (it is idempotent and creates intermediate segments).` : account ? ` Most often the account "${account}" is not configured in Notes.app: run list-accounts to check the exact name.` : "";
|
|
42152
42161
|
return errorResponse(
|
|
42153
|
-
`Failed to create note "${title}"
|
|
42162
|
+
`Failed to create note "${title}".${target} Otherwise check that Notes.app is running and this server has Automation access (run the doctor tool).`
|
|
42154
42163
|
);
|
|
42155
42164
|
}
|
|
42156
42165
|
const checklistWarning = detectChecklistAttempt(content) ?? "";
|
|
@@ -42239,7 +42248,7 @@ ${noteList}${truncationNote}${syncNote}`,
|
|
|
42239
42248
|
server.registerTool(
|
|
42240
42249
|
"get-note-content",
|
|
42241
42250
|
{
|
|
42242
|
-
description: "Use when: reading the full body text of one known note, by id (preferred) or title.\nReturns: the note's content plus parsed hashtags.\nDo not use when: you only need metadata (get-note-details) or Markdown with checklist state (get-note-markdown).\nNote: password-protected notes must be unlocked in Notes.app first.",
|
|
42251
|
+
description: "Use when: reading the full body text of one known note, by id (preferred) or title.\nReturns: the note's content plus parsed hashtags, and strippedImages/truncated when the body was capped.\nDo not use when: you only need metadata (get-note-details) or Markdown with checklist state (get-note-markdown).\nNote: password-protected notes must be unlocked in Notes.app first.\nSafety: inline images larger than APPLE_NOTES_MCP_MAX_INLINE_IMAGE_BYTES (default 256 KB) are replaced with '[inline image omitted: ...]' text placeholders, so the returned body is lossy whenever truncated is true \u2014 do NOT write it back with update-note or the real images are replaced by that text. Use append-to-note to add content, or export the images with save-attachment / fetch-attachment first.",
|
|
42243
42252
|
inputSchema: {
|
|
42244
42253
|
id: external_exports.string().max(MAX.ID).optional().describe("Note ID (preferred - more reliable than title)"),
|
|
42245
42254
|
title: external_exports.string().max(MAX.TITLE).optional().describe("Note title (use id instead when available)"),
|
|
@@ -42248,7 +42257,11 @@ server.registerTool(
|
|
|
42248
42257
|
outputSchema: {
|
|
42249
42258
|
title: external_exports.string().optional(),
|
|
42250
42259
|
content: external_exports.string().optional(),
|
|
42251
|
-
hashtags: external_exports.array(external_exports.string()).optional()
|
|
42260
|
+
hashtags: external_exports.array(external_exports.string()).optional(),
|
|
42261
|
+
/** Number of oversized inline images replaced with text placeholders. */
|
|
42262
|
+
strippedImages: external_exports.number().optional(),
|
|
42263
|
+
/** True when content is lossy — see strippedImages. Never write a truncated body back. */
|
|
42264
|
+
truncated: external_exports.boolean().optional()
|
|
42252
42265
|
}
|
|
42253
42266
|
},
|
|
42254
42267
|
withErrorHandling(({ id, title, account }) => {
|
|
@@ -42273,7 +42286,9 @@ server.registerTool(
|
|
|
42273
42286
|
return successResponse(warning2 ? content2 + warning2 : content2, {
|
|
42274
42287
|
title: note2.title,
|
|
42275
42288
|
content: content2,
|
|
42276
|
-
hashtags: hashtags2
|
|
42289
|
+
hashtags: hashtags2,
|
|
42290
|
+
strippedImages: stripped2.strippedCount,
|
|
42291
|
+
truncated: stripped2.strippedCount > 0
|
|
42277
42292
|
});
|
|
42278
42293
|
}
|
|
42279
42294
|
if (!title) {
|
|
@@ -42296,7 +42311,13 @@ server.registerTool(
|
|
|
42296
42311
|
const content = stripped.html;
|
|
42297
42312
|
const hashtags = parseHashtags(content);
|
|
42298
42313
|
const warning = strippedImagesWarning(stripped);
|
|
42299
|
-
return successResponse(warning ? content + warning : content, {
|
|
42314
|
+
return successResponse(warning ? content + warning : content, {
|
|
42315
|
+
title,
|
|
42316
|
+
content,
|
|
42317
|
+
hashtags,
|
|
42318
|
+
strippedImages: stripped.strippedCount,
|
|
42319
|
+
truncated: stripped.strippedCount > 0
|
|
42320
|
+
});
|
|
42300
42321
|
}, "Error retrieving note content")
|
|
42301
42322
|
);
|
|
42302
42323
|
server.registerTool(
|
|
@@ -42437,7 +42458,7 @@ server.registerTool(
|
|
|
42437
42458
|
server.registerTool(
|
|
42438
42459
|
"get-note-link",
|
|
42439
42460
|
{
|
|
42440
|
-
description: "Use when: you need the notes:// deep-link URL for a note so it can be stored in a Reminders task, shared, or opened directly.\nReturns: a notes://showNote?identifier=<uuid> URL that opens the note in Notes.app on iOS and macOS.\nDo not use when: you only need the note's CoreData id (get-note-by-id) or want to reveal the note on screen (show-note).\nNote:
|
|
42461
|
+
description: "Use when: you need the notes:// deep-link URL for a note so it can be stored in a Reminders task, shared, or opened directly.\nReturns: a notes://showNote?identifier=<uuid> URL that opens the note in Notes.app on iOS and macOS.\nDo not use when: you only need the note's CoreData id (get-note-by-id) or want to reveal the note on screen (show-note).\nNote: the primary path reads the note's identifier from the Notes database, so it needs Full Disk Access for the app that launches this server; macOS 12-15 can fall back to the AppleScript 'note link' property, which macOS 26+ no longer exposes. Password-protected notes cannot be linked.",
|
|
42441
42462
|
inputSchema: {
|
|
42442
42463
|
id: external_exports.string().max(MAX.ID).optional().describe("Note ID (preferred - more reliable than title)"),
|
|
42443
42464
|
title: external_exports.string().max(MAX.TITLE).optional().describe("Note title (use id instead when available)"),
|
|
@@ -43132,7 +43153,7 @@ server.registerTool(
|
|
|
43132
43153
|
return ` ${icon} ${c.name}: ${c.message}`;
|
|
43133
43154
|
}).join("\n");
|
|
43134
43155
|
const fdaAvailable = hasFullDiskAccess();
|
|
43135
|
-
const fdaLine = fdaAvailable ? " \u2713 full_disk_access: Granted (checklist
|
|
43156
|
+
const fdaLine = fdaAvailable ? " \u2713 full_disk_access: Granted (Notes database readable \u2014 checklist state, note metadata, note links, sync detail)" : ` \u24D8 full_disk_access: Not granted \u2014 get-checklist-state, get-note-metadata, and the checklist annotations in get-note-markdown won't work; get-note-link fails on macOS 26+ (macOS 12-15 falls back to AppleScript); get-sync-status cannot see pending uploads. The rest of the server is pure AppleScript and is unaffected. In System Settings > Privacy & Security > Full Disk Access, grant access to the app that launches this server (Claude Desktop / Terminal / iTerm2), then fully quit and relaunch it. Setup guide: ${FULL_DISK_ACCESS_GUIDE_URL} \u2014 run the doctor tool to verify.`;
|
|
43136
43157
|
return successResponse(`${statusIcon} ${statusText}
|
|
43137
43158
|
|
|
43138
43159
|
${checkLines}
|
|
@@ -43263,7 +43284,7 @@ server.registerTool(
|
|
|
43263
43284
|
{
|
|
43264
43285
|
description: "Use when: permanently deleting multiple notes by id in one call.\nReturns: per-id success/failure counts.\nDo not use when: deleting a single note (delete-note).\nSafety: requires explicit user confirmation; this is destructive and not undoable. Prefer search-notes/list-notes first to confirm the exact ids being deleted.",
|
|
43265
43286
|
inputSchema: {
|
|
43266
|
-
ids: external_exports.array(external_exports.string().max(MAX.ID)).max(MAX.BATCH_IDS).describe(
|
|
43287
|
+
ids: external_exports.array(external_exports.string().max(MAX.ID)).max(MAX.BATCH_IDS).describe(`Array of note IDs to delete (max ${MAX.BATCH_IDS} per request)`)
|
|
43267
43288
|
},
|
|
43268
43289
|
outputSchema: {
|
|
43269
43290
|
ok: external_exports.boolean().optional(),
|
|
@@ -43299,8 +43320,10 @@ server.registerTool(
|
|
|
43299
43320
|
{
|
|
43300
43321
|
description: "Use when: moving multiple notes by id into one destination folder.\nReturns: per-id success/failure counts.\nDo not use when: moving a single note (move-note).\nNote: the destination folder must already exist (create-folder).",
|
|
43301
43322
|
inputSchema: {
|
|
43302
|
-
ids: external_exports.array(external_exports.string().max(MAX.ID)).max(MAX.BATCH_IDS).describe(
|
|
43303
|
-
folder: external_exports.string().max(MAX.FOLDER).describe(
|
|
43323
|
+
ids: external_exports.array(external_exports.string().max(MAX.ID)).max(MAX.BATCH_IDS).describe(`Array of note IDs to move (max ${MAX.BATCH_IDS} per request)`),
|
|
43324
|
+
folder: external_exports.string().max(MAX.FOLDER).describe(
|
|
43325
|
+
'Destination folder name or nested path (e.g. "Work/Clients"). Must already exist \u2014 create-folder first.'
|
|
43326
|
+
),
|
|
43304
43327
|
account: external_exports.string().max(MAX.ACCOUNT).optional().describe("Account containing the destination folder (defaults to iCloud)")
|
|
43305
43328
|
},
|
|
43306
43329
|
outputSchema: {
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# AppleScript Limitations
|
|
2
2
|
|
|
3
3
|
Apple Notes is automated through its AppleScript dictionary. A few features that
|
|
4
|
-
exist in the Notes UI are simply **not exposed to AppleScript**, so
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
exist in the Notes UI are simply **not exposed to AppleScript**, so no script can
|
|
5
|
+
read or write them. Where this server recovers one of them anyway, it does so by
|
|
6
|
+
reading Notes' private `NoteStore.sqlite` store **read-only** — which needs
|
|
7
|
+
[Full Disk Access](./FULL-DISK-ACCESS.md). Each section below says which case it
|
|
8
|
+
is. This page documents what was investigated, how it was verified, and the
|
|
9
|
+
conclusion, so the limitation isn't re-investigated every release.
|
|
8
10
|
|
|
9
11
|
The full set of properties Notes exposes on a `note` is:
|
|
10
12
|
|
|
@@ -17,9 +19,9 @@ shared, body, id, name, plaintext
|
|
|
17
19
|
|
|
18
20
|
## Pinned notes (#28)
|
|
19
21
|
|
|
20
|
-
**Status: not feasible via AppleScript
|
|
21
|
-
the top of a folder, but the `note` class
|
|
22
|
-
it raises error `-1700`:
|
|
22
|
+
**Status: not feasible via AppleScript; readable via the NoteStore database.**
|
|
23
|
+
The Notes UI lets you pin a note to the top of a folder, but the `note` class
|
|
24
|
+
has no `pinned` property. Asking for it raises error `-1700`:
|
|
23
25
|
|
|
24
26
|
```applescript
|
|
25
27
|
tell application "Notes"
|
|
@@ -31,41 +33,61 @@ end tell
|
|
|
31
33
|
There is no alternative property, element, or command (`pin`, `pinned`,
|
|
32
34
|
`favorite`, …) in the dictionary. Pinned state lives only in Notes' private
|
|
33
35
|
Core Data store (`NoteStore.sqlite`), which is not part of the scriptable
|
|
34
|
-
surface
|
|
35
|
-
across macOS releases and outside what an AppleScript-based server should do —
|
|
36
|
-
and there is no supported way to *set* it at all.
|
|
36
|
+
surface, and there is no supported way to *set* it at all.
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
Reading it, however, did turn out to be worth doing. Since 2.5.0 the BETA
|
|
39
|
+
`get-note-metadata` tool queries `ZISPINNED` on `ZICCLOUDSYNCINGOBJECT` in that
|
|
40
|
+
store, opened **read-only**, feature-detecting each column with
|
|
41
|
+
`PRAGMA table_info` so it degrades instead of breaking when the private schema
|
|
42
|
+
changes across macOS releases. It requires
|
|
43
|
+
[Full Disk Access](./FULL-DISK-ACCESS.md) and is marked BETA precisely because
|
|
44
|
+
the schema is version-dependent.
|
|
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.
|
|
41
50
|
|
|
42
51
|
## Note-to-note links (#30)
|
|
43
52
|
|
|
44
|
-
**Status:
|
|
45
|
-
a link from one note to another in the UI, but
|
|
46
|
-
or element for
|
|
53
|
+
**Status: link *relationships* are not exposed; a shareable deep link is.**
|
|
54
|
+
Apple Notes lets you insert a link from one note to another in the UI, but
|
|
55
|
+
AppleScript exposes no property or element for that relationship:
|
|
47
56
|
|
|
48
57
|
- A `note` has no `URL`, `url`, or `link` property — each raises error `-2753`
|
|
49
58
|
(undefined). There is no element that enumerates outgoing/incoming links.
|
|
50
|
-
-
|
|
51
|
-
|
|
52
|
-
|
|
59
|
+
- Nothing in the dictionary inserts a link into a note's body.
|
|
60
|
+
|
|
61
|
+
A shareable deep link to a note *is* available, and has been since 2.6.0:
|
|
62
|
+
`get-note-link` returns a `notes://showNote?identifier=<uuid>` URL that opens
|
|
63
|
+
the note in Notes.app on macOS and iOS.
|
|
64
|
+
|
|
65
|
+
- **Primary path — the NoteStore database.** The UUID in that URL is
|
|
66
|
+
`ZIDENTIFIER` on `ZICCLOUDSYNCINGOBJECT`, read **read-only** from
|
|
67
|
+
`NoteStore.sqlite`. This works on every macOS version but needs
|
|
68
|
+
[Full Disk Access](./FULL-DISK-ACCESS.md).
|
|
69
|
+
- **Fallback — AppleScript.** On macOS 12–15 the Notes dictionary does expose a
|
|
70
|
+
two-word `note link` property, used when the database read fails. It is absent
|
|
71
|
+
from the Notes SDEF on macOS 26+, which is why the database is the primary
|
|
72
|
+
path. (`note link` is a different term from the `URL` / `url` / `link` names
|
|
73
|
+
probed above, which genuinely do not exist.)
|
|
74
|
+
- Password-protected notes return no link.
|
|
53
75
|
|
|
54
|
-
The
|
|
55
|
-
a note in the Notes UI by id:
|
|
76
|
+
The `show` command reveals an object in the Notes UI by id:
|
|
56
77
|
|
|
57
78
|
```applescript
|
|
58
79
|
tell application "Notes" to show note id "x-coredata://…/ICNote/p123"
|
|
59
80
|
```
|
|
60
81
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
82
|
+
It **is** wrapped, as `show-note`, `show-folder`, `show-account`, and
|
|
83
|
+
`show-attachment`. Those tools activate the Notes.app GUI, so they only do
|
|
84
|
+
something useful on a machine with an active desktop session; to read a note's
|
|
85
|
+
content, use `get-note-content` / `get-note-markdown` instead.
|
|
65
86
|
|
|
66
|
-
**Conclusion:**
|
|
67
|
-
|
|
68
|
-
|
|
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.
|
|
69
91
|
|
|
70
92
|
## Tags / hashtags (#29)
|
|
71
93
|
|
package/docs/FULL-DISK-ACCESS.md
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
|
-
# Full Disk Access
|
|
1
|
+
# Full Disk Access
|
|
2
2
|
|
|
3
|
-
Apple Notes MCP works almost entirely without any special disk permission.
|
|
4
|
-
|
|
5
|
-
server:
|
|
3
|
+
Apple Notes MCP works almost entirely without any special disk permission. The
|
|
4
|
+
tools that need **Full Disk Access (FDA)** for the process that runs the MCP
|
|
5
|
+
server are the ones that read Notes' own SQLite store:
|
|
6
6
|
|
|
7
7
|
- **`get-checklist-state`** — reads a note's checklist done/undone state.
|
|
8
8
|
- **Checklist annotations in `get-note-markdown`** — the `[x]` / `[ ]` prefixes on
|
|
9
9
|
checklist items.
|
|
10
|
+
- **`get-note-metadata` (BETA)** — pinned state, checklist flags, trash/recovery
|
|
11
|
+
state, snippets, password hint. These columns exist nowhere else, so this tool
|
|
12
|
+
needs FDA unconditionally.
|
|
13
|
+
- **`get-note-link`** — its primary path reads the note's `ZIDENTIFIER` from the
|
|
14
|
+
database. On macOS 12–15 it can fall back to the AppleScript `note link`
|
|
15
|
+
property; that property is absent from the Notes SDEF on macOS 26+, so there
|
|
16
|
+
FDA is the only route.
|
|
17
|
+
- **`get-sync-status`** — degrades rather than fails: without database access it
|
|
18
|
+
cannot see pending uploads or recent write activity.
|
|
10
19
|
|
|
11
20
|
Everything else (creating, reading, searching, updating, moving, deleting notes;
|
|
12
21
|
folders, accounts, attachments, stats, export, etc.) works **without** Full Disk
|
|
@@ -17,7 +26,9 @@ Access.
|
|
|
17
26
|
Apple Notes stores checklist items as a paragraph style inside a gzipped protobuf
|
|
18
27
|
blob in its SQLite store, `NoteStore.sqlite`. AppleScript's `body of note`
|
|
19
28
|
interface strips that state — it can't tell you whether a checklist item is
|
|
20
|
-
checked.
|
|
29
|
+
checked. The same store also holds the pinned/trash/snippet columns behind
|
|
30
|
+
`get-note-metadata` and the `ZIDENTIFIER` value behind `get-note-link`. To
|
|
31
|
+
recover any of them, the MCP reads the SQLite store directly.
|
|
21
32
|
|
|
22
33
|
That database lives in a macOS-protected directory:
|
|
23
34
|
|
|
@@ -26,9 +37,8 @@ That database lives in a macOS-protected directory:
|
|
|
26
37
|
```
|
|
27
38
|
|
|
28
39
|
Reading anything under `~/Library/Group Containers/` requires **Full Disk
|
|
29
|
-
Access** for the host process — without it, macOS denies the read
|
|
30
|
-
|
|
31
|
-
writes to it.)
|
|
40
|
+
Access** for the host process — without it, macOS denies the read. (The MCP only
|
|
41
|
+
ever **reads** this database; it never writes to it.)
|
|
32
42
|
|
|
33
43
|
## How to grant Full Disk Access
|
|
34
44
|
|
|
@@ -64,9 +74,16 @@ The server degrades gracefully — nothing crashes:
|
|
|
64
74
|
|
|
65
75
|
- `get-checklist-state` returns a clear error explaining that database access is
|
|
66
76
|
needed (and points here).
|
|
77
|
+
- `get-note-metadata` returns the same kind of error — it has no non-database
|
|
78
|
+
path, so it cannot answer at all without FDA.
|
|
79
|
+
- `get-note-link` returns an error on macOS 26+. On macOS 12–15 it still works,
|
|
80
|
+
via the AppleScript `note link` fallback.
|
|
67
81
|
- `get-note-markdown` still returns the note as Markdown, but checklist items
|
|
68
82
|
appear as plain list items without the `[x]`/`[ ]` annotations.
|
|
69
|
-
-
|
|
83
|
+
- `get-sync-status` still answers, but with no database visibility it reports no
|
|
84
|
+
pending uploads and no active sync — treat that as "unknown", not "idle".
|
|
85
|
+
- **Every other tool works normally**, since the rest of the server is pure
|
|
86
|
+
AppleScript.
|
|
70
87
|
|
|
71
88
|
See also: [Known Limitations](../README.md#known-limitations) and
|
|
72
89
|
[Creating Checklists](../README.md#creating-checklists) in the README.
|
|
@@ -11,29 +11,33 @@ of code already hardened in apple-mail. Line numbers are against `main` @ `bb677
|
|
|
11
11
|
|
|
12
12
|
## Resolution status
|
|
13
13
|
|
|
14
|
+
All seventeen findings are closed; issues #16–#32 are all closed on GitHub. The
|
|
15
|
+
body below is kept as the dated 1.4.4 snapshot that motivated the 2.0 line — it
|
|
16
|
+
describes the code as it was on 2026-06-19, **not** as it is today.
|
|
17
|
+
|
|
14
18
|
| # | Finding | Tier | Issue | Status |
|
|
15
19
|
|---|---------|------|-------|--------|
|
|
16
|
-
| H1 | `execSync` has no `maxBuffer` cap | High | [#16](https://github.com/sweetrb/apple-notes-mcp/issues/16) |
|
|
17
|
-
| H2 | No `with timeout` + SIGTERM wedges Notes.app | High | [#17](https://github.com/sweetrb/apple-notes-mcp/issues/17) |
|
|
18
|
-
| H3 | Printable `\|\|\|` / comma delimiters collide with user content | High | [#18](https://github.com/sweetrb/apple-notes-mcp/issues/18) |
|
|
19
|
-
| H4 | Swallowed failures return `[]`/`null`/`0` | High | [#19](https://github.com/sweetrb/apple-notes-mcp/issues/19) |
|
|
20
|
-
| H5 | Unbounded full-library scans, no partial-result signal | High | [#20](https://github.com/sweetrb/apple-notes-mcp/issues/20) |
|
|
21
|
-
| M1 | No `structuredContent` on any tool | Medium | [#21](https://github.com/sweetrb/apple-notes-mcp/issues/21) |
|
|
22
|
-
| M2 | No `doctor` tool (incl. Full Disk Access check) | Medium | [#22](https://github.com/sweetrb/apple-notes-mcp/issues/22) |
|
|
23
|
-
| M3 | No MCP resources or prompts | Medium | [#23](https://github.com/sweetrb/apple-notes-mcp/issues/23) |
|
|
24
|
-
| M4 | No file-based config loader | Medium | [#24](https://github.com/sweetrb/apple-notes-mcp/issues/24) |
|
|
25
|
-
| M5 | Locale-fragile date parsing | Medium | [#25](https://github.com/sweetrb/apple-notes-mcp/issues/25) |
|
|
26
|
-
| M6 | Batch ops are N+1 osascript fan-out | Medium | [#26](https://github.com/sweetrb/apple-notes-mcp/issues/26) |
|
|
27
|
-
| M7 | No `save-attachment` / `fetch-attachment` | Medium | [#27](https://github.com/sweetrb/apple-notes-mcp/issues/27) |
|
|
28
|
-
| L1 | Pinned notes not exposed | Low | [#28](https://github.com/sweetrb/apple-notes-mcp/issues/28) |
|
|
29
|
-
| L2 | Tags/hashtags not surfaced | Low | [#29](https://github.com/sweetrb/apple-notes-mcp/issues/29) |
|
|
30
|
-
| L3 | Note-to-note links not supported | Low | [#30](https://github.com/sweetrb/apple-notes-mcp/issues/30) |
|
|
31
|
-
| L5 | No integration test suite | Low | [#31](https://github.com/sweetrb/apple-notes-mcp/issues/31) |
|
|
32
|
-
| L6 | Full Disk Access guide + commit this audit | Low | [#32](https://github.com/sweetrb/apple-notes-mcp/issues/32) |
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
20
|
+
| H1 | `execSync` has no `maxBuffer` cap | High | [#16](https://github.com/sweetrb/apple-notes-mcp/issues/16) | Shipped 2.0.0 (64 MB default, `APPLE_NOTES_MCP_MAX_BUFFER`) |
|
|
21
|
+
| H2 | No `with timeout` + SIGTERM wedges Notes.app | High | [#17](https://github.com/sweetrb/apple-notes-mcp/issues/17) | Shipped 2.0.0 (`with timeout` wrap + `killSignal: SIGKILL`) |
|
|
22
|
+
| H3 | Printable `\|\|\|` / comma delimiters collide with user content | High | [#18](https://github.com/sweetrb/apple-notes-mcp/issues/18) | Shipped 2.0.0 (ASCII `\x1f` / `\x1e` delimiters) |
|
|
23
|
+
| H4 | Swallowed failures return `[]`/`null`/`0` | High | [#19](https://github.com/sweetrb/apple-notes-mcp/issues/19) | Shipped 2.0.0 (failures surface as MCP errors) + 2.1.0 (`get-notes-stats` partial-coverage reporting) |
|
|
24
|
+
| H5 | Unbounded full-library scans, no partial-result signal | High | [#20](https://github.com/sweetrb/apple-notes-mcp/issues/20) | Shipped 2.0.0 (server-side counting) |
|
|
25
|
+
| M1 | No `structuredContent` on any tool | Medium | [#21](https://github.com/sweetrb/apple-notes-mcp/issues/21) | Shipped 2.0.0, completed 2.2.0 (all tools) and 2.3.0 (`outputSchema` on all tools) |
|
|
26
|
+
| M2 | No `doctor` tool (incl. Full Disk Access check) | Medium | [#22](https://github.com/sweetrb/apple-notes-mcp/issues/22) | Shipped 2.0.0 |
|
|
27
|
+
| M3 | No MCP resources or prompts | Medium | [#23](https://github.com/sweetrb/apple-notes-mcp/issues/23) | Shipped 2.0.0 |
|
|
28
|
+
| M4 | No file-based config loader | Medium | [#24](https://github.com/sweetrb/apple-notes-mcp/issues/24) | Shipped 2.0.0 |
|
|
29
|
+
| M5 | Locale-fragile date parsing | Medium | [#25](https://github.com/sweetrb/apple-notes-mcp/issues/25) | Shipped 2.0.0 |
|
|
30
|
+
| M6 | Batch ops are N+1 osascript fan-out | Medium | [#26](https://github.com/sweetrb/apple-notes-mcp/issues/26) | Shipped 2.1.0 (one osascript spawn per batch; explicitly deferred in 2.0.0) |
|
|
31
|
+
| M7 | No `save-attachment` / `fetch-attachment` | Medium | [#27](https://github.com/sweetrb/apple-notes-mcp/issues/27) | Shipped 2.0.0 |
|
|
32
|
+
| L1 | Pinned notes not exposed | Low | [#28](https://github.com/sweetrb/apple-notes-mcp/issues/28) | Partial — AppleScript infeasibility documented 2.1.0 ([APPLESCRIPT-LIMITATIONS](./APPLESCRIPT-LIMITATIONS.md)); pin state made **readable** in 2.5.0 via the BETA `get-note-metadata` NoteStore path. Setting is still unsupported |
|
|
33
|
+
| L2 | Tags/hashtags not surfaced | Low | [#29](https://github.com/sweetrb/apple-notes-mcp/issues/29) | Shipped 2.1.0, read-only — `get-note-content` parses inline `#hashtags`; `create-note`'s `tags` param stays a cosmetic pass-through |
|
|
34
|
+
| L3 | Note-to-note links not supported | Low | [#30](https://github.com/sweetrb/apple-notes-mcp/issues/30) | Partial — closed 2.1.0 as documented-infeasible, then superseded in 2.6.0 by `get-note-link` (`notes://showNote?identifier=<uuid>`). Reading link *relationships*, and inserting a link, remain infeasible |
|
|
35
|
+
| L5 | No integration test suite | Low | [#31](https://github.com/sweetrb/apple-notes-mcp/issues/31) | Shipped 2.1.0 (`test/integration.test.ts` + the required `integration` CI job) |
|
|
36
|
+
| L6 | Full Disk Access guide + commit this audit | Low | [#32](https://github.com/sweetrb/apple-notes-mcp/issues/32) | Shipped 2.0.0 ([FULL-DISK-ACCESS.md](./FULL-DISK-ACCESS.md)) |
|
|
37
|
+
|
|
38
|
+
The fixes landed across the 2.0.0–2.6.x line rather than in a single release;
|
|
39
|
+
per-item detail is in [CHANGELOG.md](../CHANGELOG.md). This document is retained
|
|
40
|
+
as a historical record and is not a live backlog.
|
|
37
41
|
|
|
38
42
|
---
|
|
39
43
|
|
package/package.json
CHANGED