apple-notes-mcp 2.9.10 → 2.9.12

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
@@ -982,6 +982,13 @@ Deletes a note (moves to Recently Deleted in Notes.app).
982
982
  Title-only deletion is rejected. If the note changed after the supplied hash
983
983
  was read, deletion is also rejected.
984
984
 
985
+ **Notes with large images.** Notes.app returns a note's body with each inline
986
+ image embedded as base64, so a note holding one 40 MB image has a body of about
987
+ 110 MB. Body reads accept up to 512 MB of output, and `delete-note` compares a
988
+ body longer than 5 MB against a private temporary file rather than embedding it
989
+ in the AppleScript. The comparison still covers the whole body, and the file
990
+ is removed when the delete finishes.
991
+
985
992
  **Copy-then-retire.** To delete an original only while its copy is still good,
986
993
  read both notes, verify the copy, and pass the copy as `guardNoteId` with its
987
994
  `contentHash` as `expectedGuardContentHash`. The copy's revision is re-read just
@@ -1857,6 +1864,37 @@ Decodes a note's classic PencilKit drawings (`com.apple.drawing.2` and the older
1857
1864
 
1858
1865
  ---
1859
1866
 
1867
+ #### `transcribe-note-audio`
1868
+
1869
+ Transcribes a note's voice recordings and audio attachments now, on this Mac, with Apple's Speech framework through the [public native helper](#public-native-helper). Recognition is on-device only: `SpeechAnalyzer` on macOS 26 and later, or `SFSpeechRecognizer` with on-device recognition required on older systems (a locale without on-device support is refused, never sent to a server). The audio files are opened read-only where Notes keeps them.
1870
+
1871
+ **Requires:** Full Disk Access for the MCP host process, and the public native helper built once with `apple-notes-mcp setup --public-helper`.
1872
+
1873
+ | Parameter | Type | Required | Description |
1874
+ |-----------|------|----------|-------------|
1875
+ | `id` | string | Yes | Note ID (use `search-notes` to find it first) |
1876
+ | `locale` | string | No | BCP-47 language of the speech, such as `"en-US"` (default), `"it-IT"`, or `"fr-FR"` |
1877
+ | `attachmentId` | string | No | Only transcribe this audio attachment (an `x-coredata://…/ICAttachment/pN` id) |
1878
+ | `includeText` | boolean | No | Include transcript text (default `true`); `false` returns statuses and word counts only |
1879
+ | `downloadAssets` | boolean | No | Let macOS download the language's on-device speech model when it is missing (default `false`: the call returns `asset_unavailable` at once) |
1880
+ | `maxSeconds` | integer | No | Total time budget for the call, 30 to 3600 seconds (default 900) |
1881
+
1882
+ **Returns:** Overall `status` and, per audio attachment, `status`, `durationSeconds`, `wordCount`, `transcript`, and `takes` (a Notes recording can hold several takes; each is transcribed and the texts are joined in stored order). Statuses:
1883
+
1884
+ - `ok`: the whole recording was transcribed.
1885
+ - `partial`: some text came back, but a take failed or the helper stopped at its deadline (`code: "incomplete"`).
1886
+ - `error`: nothing was transcribed; `code` says why (`asset_unavailable` when the audio file is not on this Mac or the language's speech model is not installed, `permission_required`, `unsupported_locale`, `unsupported_audio`, `time_limit`, ...).
1887
+ - `indeterminate`: the helper did not answer in time, so the outcome is unknown and a retry may succeed.
1888
+ - `none` (overall only): the note has no audio.
1889
+
1890
+ Each take gets a deadline of 1.5 times its length plus a minute (at most 30 minutes), shortened to what is left of `maxSeconds`. A take that cannot start before the budget runs out reports `code: "time_limit"`. The helper runs as a separate process without blocking the server, and cancelling the request stops it. On macOS 27 a 16-minute recording took about 22 seconds. Some MCP clients stop waiting for a tool after a fixed time, so transcribe long recordings one at a time with `attachmentId`. Transcripts longer than `APPLE_NOTES_MCP_EXPORT_MAX_BYTES` are shortened and marked `transcriptTruncated`.
1891
+
1892
+ **Speech models:** the call never starts a download on its own. When the language's on-device model is not installed, it returns `asset_unavailable` right away. Pass `downloadAssets: true` to let macOS download it (a one-time download); if it is still downloading when the call ends, try again shortly.
1893
+
1894
+ **Speech Recognition permission:** the server never shows the Speech Recognition prompt, because nobody may be watching an MCP server to answer it. The helper reads the current authorization first. On older macOS, the `SFSpeechRecognizer` path needs that access, and macOS attributes the grant to the app that launches the MCP server (Claude Desktop, Codex, Terminal, and so on), not to the helper. Without it the call returns `code: "permission_required"`; allow the app under System Settings > Privacy & Security > Speech Recognition. On macOS 26 and later, `SpeechAnalyzer` transcribed files without any grant in testing (authorization stayed "not determined"), so only an explicit refusal (denied or restricted) stops it.
1895
+
1896
+ ---
1897
+
1860
1898
  #### `add-attachment`
1861
1899
 
1862
1900
  Adds one nonempty local file of at most 64 MiB to an exact note using `id`, the
@@ -2431,7 +2469,7 @@ All configuration is optional — the server works out of the box. Override beha
2431
2469
 
2432
2470
  | Variable | Default | Description |
2433
2471
  |----------|---------|-------------|
2434
- | `APPLE_NOTES_MCP_MAX_BUFFER` | `67108864` (64 MB) | Max bytes captured from a single AppleScript invocation. Raise it if a very large export/list is truncated; lower it to cap memory. |
2472
+ | `APPLE_NOTES_MCP_MAX_BUFFER` | `67108864` (64 MB) | Max bytes captured from a single AppleScript invocation. Raise it if a very large export/list is truncated; lower it to cap memory. Reading one note body allows at least 512 MB regardless, since a body embeds its inline images. Output past the cap fails with an error naming this variable, not as a timeout. |
2435
2473
  | `APPLE_NOTES_MCP_MAX_ATTACHMENT_BYTES` | `26214400` (25 MB) | Max size of an attachment that [`fetch-attachment`](#fetch-attachment) will base64-encode inline. Larger attachments are rejected with an error pointing at [`save-attachment`](#save-attachment) (which streams to disk and has no such limit). Raise it to fetch bigger attachments inline; lower it to cap memory. |
2436
2474
  | `APPLE_NOTES_MCP_MAX_INLINE_IMAGE_BYTES` | `262144` (256 KB) | Per-image cap on the base64 payload kept inline in a [`get-note-content`](#get-note-content) response. Inline images over the cap are replaced with placeholders (with a warning appended) so an image-heavy note cannot exceed the MCP client's message limit and drop the connection; export the real files with [`save-attachment`](#save-attachment) or [`fetch-attachment`](#fetch-attachment). Raise it to keep bigger images inline. |
2437
2475
  | `APPLE_NOTES_MCP_CONFIG_FILE` | `~/Library/Application Support/apple-notes-mcp/config.json` | Path to the JSON config file (see below). |
@@ -2496,13 +2534,13 @@ Every tool that does not read the Notes database works normally without Full Dis
2496
2534
  - `get-note-link` returns an error on macOS 26+; on macOS 12–15 it still works via the AppleScript `note link` fallback
2497
2535
  - `get-note-markdown` returns plain list items without `[x]`/`[ ]` annotations (graceful fallback)
2498
2536
  - `get-sync-status` still answers, but reports no pending uploads and no active sync — treat that as "unknown", not "idle"
2499
- - `get-note-drawings` returns the same Full Disk Access error
2537
+ - `get-note-drawings` and `transcribe-note-audio` return the same Full Disk Access error
2500
2538
 
2501
2539
  ---
2502
2540
 
2503
2541
  ## Public native helper
2504
2542
 
2505
- `get-note-drawings` needs Apple's PencilKit framework, which has no AppleScript or command-line interface. For it, the server uses a small Swift helper that links public Apple frameworks only (AppKit and PencilKit). No prebuilt binary ships with the package. Build it once on your Mac:
2543
+ `get-note-drawings` needs Apple's PencilKit framework and `transcribe-note-audio` needs the Speech framework; neither has an AppleScript or command-line interface. For them, the server uses a small Swift helper that links public Apple frameworks only (AppKit, PencilKit, AVFoundation, and Speech). No prebuilt binary ships with the package. Build it once on your Mac:
2506
2544
 
2507
2545
  ```bash
2508
2546
  apple-notes-mcp setup --public-helper # compile, sign, verify, install
@@ -2511,7 +2549,7 @@ apple-notes-mcp setup --public-helper --check # report the installed state only
2511
2549
 
2512
2550
  Setup compiles `native/public-helper/apple-notes-public-helper.swift` with `xcrun swiftc` (install the Command Line Tools with `xcode-select --install` if it is missing), signs it ad hoc, runs its `hello` handshake, and installs it in `~/Library/Application Support/apple-notes-mcp/public-helper/` next to a manifest recording the SHA-256 of the source and the binary. Before every use the server re-checks both digests: after an upgrade that changes the helper source, or if the binary is replaced, the helper is refused until you run setup again. `APPLE_NOTES_MCP_PUBLIC_HELPER_DIR` overrides the install folder and `APPLE_NOTES_MCP_PUBLIC_HELPER_TIMEOUT_MS` the per-call timeout.
2513
2551
 
2514
- The helper never opens the Notes database and never writes under the Notes group container. The server reads the bytes it needs (read-only) and passes them to the helper on stdin; the helper answers with one JSON object on stdout.
2552
+ The helper never opens the Notes database and never writes under the Notes group container. The server reads the bytes it needs (read-only) and passes them to the helper on stdin, or, for transcription, names one audio file that the helper opens for reading; the helper answers with one JSON object on stdout.
2515
2553
 
2516
2554
  ---
2517
2555