apple-notes-mcp 2.8.13 → 2.8.15

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
@@ -71,6 +71,16 @@ workflows. Confirm **Add Shortcut** in each macOS window, then verify with
71
71
  support silent Shortcut import, so merely connecting an MCP client never opens
72
72
  setup windows or bypasses these confirmations.
73
73
 
74
+ After install **and after every upgrade**, open Shortcuts.app and run each
75
+ bridge — `Apple Notes MCP - Native Tags` and
76
+ `Apple Notes MCP - Background Operations v5` — once in the foreground, choosing
77
+ **Always Allow** when Shortcuts asks for permission. The server runs these
78
+ Shortcuts in the background, where Shortcuts cannot display a first-run consent
79
+ prompt: an unanswered one stalls every native write on that bridge until it
80
+ times out, while `doctor` still reports the bridge installed. Quitting or
81
+ relaunching Shortcuts.app or Notes.app does not clear it; the foreground run
82
+ does, once per bridge.
83
+
74
84
  ### Using the Codex Marketplace
75
85
 
76
86
  The same plugin is available for Codex. Add the marketplace and install the plugin:
@@ -225,6 +235,14 @@ Creates a new note in Apple Notes.
225
235
 
226
236
  > **Note:** The title is automatically prepended as `<h1>` in both plaintext and HTML formats. Do not include a `<h1>` title tag in the `content` parameter, or the title will appear twice.
227
237
 
238
+ > **Known limitation:** `create-note` sets the note body directly via AppleScript's
239
+ > `body` property, which does not apply real Notes paragraph styles for interior
240
+ > content — an `<h2>`/`<h3>` tag or a `<span style="font-size: …px">` heading span
241
+ > in `content` renders as plain bold, styled text, not an actual Heading or
242
+ > Subheading ([#172](https://github.com/sweetrb/apple-notes-mcp/issues/172)). Real
243
+ > Heading/Subheading styles can currently only be added to a note that already
244
+ > exists, with [`append-native`](#append-native)'s `format: "markdown"`.
245
+
228
246
  **Returns:** Confirmation message with note title and ID. Save the ID for subsequent operations like `update-note`, `delete-note`, etc.
229
247
 
230
248
  ---
@@ -339,7 +357,10 @@ Access.
339
357
  #### `native-tags-status`
340
358
 
341
359
  Checks whether exactly one configured Native Tags Shortcut is installed. An
342
- installed workflow may still need macOS permission on its first execution.
360
+ installed workflow may still need macOS permission on its first execution, and
361
+ installation does not show whether that consent was given: run it once in the
362
+ foreground in Shortcuts.app and choose **Always Allow** after install or upgrade
363
+ (see [Troubleshooting](#native-writes-time-out-or-report-an-uncertain-outcome)).
343
364
 
344
365
  ---
345
366
 
@@ -1048,7 +1069,10 @@ Lists all notes shared with collaborators.
1048
1069
  ### Native background operations
1049
1070
 
1050
1071
  Install the signed workflows once as described in
1051
- [`shortcuts/README.md`](shortcuts/README.md). Native writes never use UI
1072
+ [`shortcuts/README.md`](shortcuts/README.md), then run each once in the
1073
+ foreground in Shortcuts.app and choose **Always Allow** — again after every
1074
+ upgrade — so no first-run consent prompt is left for a background run that
1075
+ cannot display it. Native writes never use UI
1052
1076
  automation or write directly to the Notes database. Every operation requires an
1053
1077
  exact note ID, a fresh `expectedContentHash`, and a distinctive existing
1054
1078
  `scopeText` so the Shortcut and server can independently resolve the same note.
@@ -1066,9 +1090,16 @@ when `scopeText` is supplied. Use a distinctive phrase of plain words without
1066
1090
  punctuation, hashtags, or paths because Notes search may not resolve them
1067
1091
  literally. `format: "html"` accepts the fixed subset tabulated under
1068
1092
  [`append-to-note`](#append-to-note); content outside it is refused by name, with
1069
- the accepted subset in the error. A transport failure names the Shortcut it was
1070
- waiting on, so a missing or unapproved bridge is identified rather than guessed
1071
- at.
1093
+ the accepted subset in the error. `format: "markdown"` accepts the same bounded
1094
+ subset as `format: "html"`'s Markdown-shaped content (`#`/`##`/`###` headings,
1095
+ flat lists, emphasis, and inline links) but is sent through Notes' own native
1096
+ Markdown importer rather than converted to HTML first, so `#`/`##`/`###`
1097
+ produce real Title/Heading/Subheading — Notes' HTML importer only
1098
+ distinguishes two heading levels and renders `###` the same as `##`. A
1099
+ transport failure names the Shortcut it was waiting on, so a missing or
1100
+ unapproved bridge is identified rather than guessed at; a timeout also says it
1101
+ may be an unanswered first-run consent prompt and names the Shortcut to run once
1102
+ in the foreground.
1072
1103
 
1073
1104
  #### `create-checklist-item`
1074
1105
 
@@ -1383,6 +1414,12 @@ In a JSON string literal the two characters `\\` denote **one** literal backslas
1383
1414
  - Go to System Settings > Privacy & Security > Automation
1384
1415
  - Ensure your terminal/Claude has permission to control Notes
1385
1416
 
1417
+ ### Native writes time out or report an uncertain outcome
1418
+ - Symptom: `add-native-tags`, `set-note-pinned`, `append-native` or another native write fails with "Shortcuts timed out waiting for …", "Operation outcome uncertain" or "readback was not verified", while `doctor` and `get-capabilities` report the bridges installed
1419
+ - Common cause, especially right after install or upgrade: the bridge Shortcut is waiting on a first-run consent prompt. The server runs it in the background, where Shortcuts cannot display that prompt, so the run stalls until it times out
1420
+ - Fix: open Shortcuts.app, run the Shortcut the error names (`Apple Notes MCP - Native Tags` or `Apple Notes MCP - Background Operations v5`) once in the foreground and choose **Always Allow**. Each bridge needs this once; quitting or relaunching Shortcuts.app or Notes.app does not clear it
1421
+ - Read the exact note before retrying — the write may have landed
1422
+
1386
1423
  ### "Note not found"
1387
1424
  - Note titles must match exactly (case-sensitive)
1388
1425
  - Check if the note is in a different account
package/build/index.js CHANGED
@@ -39800,10 +39800,12 @@ function htmlLinks(html) {
39800
39800
  }
39801
39801
  return links;
39802
39802
  }
39803
+ var BARE_HTTP_ORIGIN = /^https?:\/\/[^/?#]+$/i;
39804
+ var normalizedUrl = (url) => BARE_HTTP_ORIGIN.test(url) ? `${url}/` : url;
39803
39805
  function linkSignature(links) {
39804
39806
  return JSON.stringify(
39805
39807
  links.flatMap(
39806
- (link) => normalized(link.text).split("").map((char) => [char, link.url])
39808
+ (link) => normalized(link.text).split("").map((char) => [char, normalizedUrl(link.url)])
39807
39809
  )
39808
39810
  );
39809
39811
  }
@@ -42734,6 +42736,14 @@ import { execFileSync as execFileSync6 } from "node:child_process";
42734
42736
  import { mkdtempSync as mkdtempSync2, writeFileSync, rmSync as rmSync2 } from "node:fs";
42735
42737
  import { tmpdir as tmpdir2 } from "node:os";
42736
42738
  import { join as join7 } from "node:path";
42739
+
42740
+ // src/services/shortcutConsent.ts
42741
+ function shortcutConsentHint(shortcut) {
42742
+ const target = shortcut ? `"${shortcut}"` : "the bridge Shortcut";
42743
+ return `This may be an unanswered first-run Shortcuts consent prompt, which a background run cannot display: run ${target} once in the foreground in Shortcuts.app and choose Always Allow.`;
42744
+ }
42745
+
42746
+ // src/services/nativeTags.ts
42737
42747
  var NATIVE_TAGS_SHORTCUT = "Apple Notes MCP - Native Tags";
42738
42748
  function normalizeNativeTags(tags) {
42739
42749
  if (!tags.length || tags.length > 100) throw new Error("Provide between 1 and 100 tags");
@@ -42788,7 +42798,7 @@ function addNativeTags(request, deps) {
42788
42798
  };
42789
42799
  if (after.title !== before.title || allTags.some((tag) => !after.rich.nativeTags.includes(tag)) || textWithoutTags(before.rich.text) !== textWithoutTags(after.rich.text) || linkSignature(before.rich.links) !== linkSignature(after.rich.links) || before.rich.nativeObjectIds.some((id2) => !after.rich.nativeObjectIds.includes(id2)) || before.rich.hasChecklist !== after.rich.hasChecklist) {
42790
42800
  throw new Error(
42791
- "Shortcuts ran, but exact-ID tags/text/links readback was not verified. Read the note before any retry"
42801
+ transportWarning ? `Shortcuts did not complete cleanly, and exact-ID tags/text/links readback was not verified. Read the note before any retry. ${transportWarning}` : "Shortcuts ran, but exact-ID tags/text/links readback was not verified. Read the note before any retry"
42792
42802
  );
42793
42803
  }
42794
42804
  return {
@@ -42831,9 +42841,12 @@ function runNativeTagsShortcut(input) {
42831
42841
  maxBuffer: 1024 * 1024,
42832
42842
  stdio: ["ignore", "pipe", "pipe"]
42833
42843
  });
42834
- } catch {
42835
- throw new Error(
42836
- `Native tag operation did not complete cleanly; the "${status.shortcut}" Shortcut may be waiting for macOS permission. Do not retry automatically; read the exact note and check that Shortcut in Shortcuts.app`
42844
+ } catch (error2) {
42845
+ throw Object.assign(
42846
+ new Error(
42847
+ `Native tag operation did not complete cleanly. ${shortcutConsentHint(status.shortcut)} Do not retry automatically; read the exact note first`
42848
+ ),
42849
+ { shortcut: status.shortcut, code: error2?.code }
42837
42850
  );
42838
42851
  } finally {
42839
42852
  rmSync2(directory, { recursive: true, force: true });
@@ -43117,7 +43130,7 @@ function mutateBackground(request, operation, data, verify, deps) {
43117
43130
  transportUncertain = true;
43118
43131
  const detail = error2;
43119
43132
  const named = detail?.shortcut ? `the "${detail.shortcut}" Shortcut` : "the background Shortcut";
43120
- transportMessage = detail?.code === "ETIMEDOUT" ? `Shortcuts timed out waiting for ${named}; check for an interactive parameter or permission request` : `${named} failed: ${String(detail?.stderr || detail?.message || "no output").trim().slice(0, 400)}`;
43133
+ transportMessage = detail?.code === "ETIMEDOUT" ? `Shortcuts timed out waiting for ${named}. ${shortcutConsentHint(detail.shortcut)}` : `${named} failed: ${String(detail?.stderr || detail?.message || "no output").trim().slice(0, 400)}`;
43121
43134
  }
43122
43135
  const after = deps.read(request.id);
43123
43136
  try {
@@ -43157,15 +43170,10 @@ function assertAppendedVisibleText(beforeHtml, afterHtml, expected) {
43157
43170
  }
43158
43171
  function appendNative(manager, request) {
43159
43172
  validateAppendContent(request.content, request.format);
43160
- if (request.format === "markdown")
43161
- return appendNative(manager, {
43162
- ...request,
43163
- content: appendMarkdownHtml(request.content),
43164
- format: "html"
43165
- });
43166
43173
  if (request.format === "html" && /<table\b/i.test(request.content))
43167
43174
  throw new Error("Use create-table for verified native table insertion");
43168
- const op = request.format === "plaintext" ? "append-text" : request.format === "html" ? "append-html" : "append-markdown";
43175
+ const markdownHtml = request.format === "markdown" ? appendMarkdownHtml(request.content) : null;
43176
+ const op = request.format === "plaintext" ? "append-text" : request.format === "markdown" ? "append-markdown" : "append-html";
43169
43177
  const text = request.format === "html" ? "<div><br></div>" + request.content : "\n\n" + request.content;
43170
43178
  return mutateBackground(
43171
43179
  request,
@@ -43173,10 +43181,11 @@ function appendNative(manager, request) {
43173
43181
  { text },
43174
43182
  (before, after) => {
43175
43183
  assertPreserved(before, after, { append: true });
43176
- const expected = request.format === "html" ? comparableVisibleText(request.content) : request.format === "plaintext" ? request.content : null;
43184
+ const verifyHtml = markdownHtml ?? request.content;
43185
+ const expected = request.format === "plaintext" ? request.content : comparableVisibleText(verifyHtml);
43177
43186
  if (expected) assertAppendedVisibleText(before.html, after.html, expected);
43178
- if (request.format === "html")
43179
- assertAppendedHtmlLinks(before.rich.links.length, after.rich.links, request.content);
43187
+ if (request.format === "html" || request.format === "markdown")
43188
+ assertAppendedHtmlLinks(before.rich.links.length, after.rich.links, verifyHtml);
43180
43189
  },
43181
43190
  backgroundDependencies(manager)
43182
43191
  );
@@ -43241,6 +43250,7 @@ function runDoctor(manager) {
43241
43250
  status: fda ? "ok" : "warn",
43242
43251
  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}`
43243
43252
  });
43253
+ const consentReminder = "After install or upgrade, run each bridge Shortcut once in the foreground in Shortcuts.app and choose Always Allow: a background run cannot display a first-run consent prompt, so an unanswered one stalls that bridge's native writes until they time out while this check stays ok";
43244
43254
  try {
43245
43255
  const bridgeStatuses = [NATIVE_TAGS_SHORTCUT, BACKGROUND_SHORTCUT].map(
43246
43256
  (name) => nativeTagsStatus(name)
@@ -43249,7 +43259,7 @@ function runDoctor(manager) {
43249
43259
  checks.push({
43250
43260
  name: "Native write Shortcuts",
43251
43261
  status: missing.length ? "warn" : "ok",
43252
- detail: missing.length ? `missing: ${missing.map((status) => status.shortcut).join(", ")}. Run apple-notes-mcp setup and approve Add Shortcut in macOS` : "both native-write bridges are installed"
43262
+ detail: missing.length ? `missing: ${missing.map((status) => status.shortcut).join(", ")}. Run apple-notes-mcp setup and approve Add Shortcut in macOS. ${consentReminder}` : `both native-write bridges are installed. ${consentReminder}`
43253
43263
  });
43254
43264
  } catch (error2) {
43255
43265
  checks.push({
@@ -44263,6 +44273,9 @@ function formatShortcutSetup(report) {
44263
44273
  lines.push(
44264
44274
  "After approving the macOS dialogs, run `apple-notes-mcp setup --check` or the MCP doctor tool."
44265
44275
  );
44276
+ lines.push(
44277
+ "After install or upgrade, run each bridge once in the foreground in Shortcuts.app and choose Always Allow; a background run cannot display a first-run consent prompt and stalls until it times out."
44278
+ );
44266
44279
  return lines.join("\n");
44267
44280
  }
44268
44281
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-notes-mcp",
3
- "version": "2.8.13",
3
+ "version": "2.8.15",
4
4
  "description": "MCP server for Apple Notes - create, search, update, and manage notes via Claude and other AI assistants",
5
5
  "type": "module",
6
6
  "main": "build/index.js",
@@ -15,6 +15,13 @@ Open the signed file and confirm **Add Shortcut** once. macOS may also request
15
15
  Notes access on first execution. The `shortcuts` CLI can run an installed
16
16
  workflow but cannot silently import one.
17
17
 
18
+ After install and after every upgrade, run each bridge once in the foreground in
19
+ Shortcuts.app and choose **Always Allow** when it asks for permission. The
20
+ server runs bridges in the background, where Shortcuts cannot display a
21
+ first-run consent prompt, so an unanswered one stalls that bridge's native
22
+ writes until they time out. Quitting or relaunching Shortcuts.app or Notes.app
23
+ does not clear it; one foreground run per bridge does.
24
+
18
25
  The bridge receives a private temporary JSON file with a title, a distinctive
19
26
  existing scope phrase, and normalized tags. Both the server and workflow refuse
20
27
  ambiguous note selection. Server readback, rather than CLI output, establishes
@@ -38,6 +45,7 @@ shortcuts sign --mode anyone \
38
45
  --output "shortcuts/Apple Notes MCP - Background Operations v5.shortcut"
39
46
  ```
40
47
 
41
- Open the signed file and confirm **Add Shortcut** once. Do not install obsolete
48
+ Open the signed file and confirm **Add Shortcut** once, then run it once in the
49
+ foreground in Shortcuts.app and choose **Always Allow** (see above). Do not install obsolete
42
50
  workflow versions with the same display name; the server resolves one exact
43
51
  installed UUID and refuses duplicates.