apple-notes-mcp 2.8.40 → 2.8.42

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
@@ -1602,7 +1602,7 @@ Saves a note attachment to disk.
1602
1602
  |-----------|------|----------|-------------|
1603
1603
  | `noteId` | string | Yes | CoreData note ID (from `search-notes`/`list-notes`) |
1604
1604
  | `attachmentId` | string | Yes | Attachment ID (from `list-attachments`) |
1605
- | `savePath` | string | Yes | Absolute destination file path. Must be under your home directory, a temp directory, or `/Volumes` |
1605
+ | `savePath` | string | Yes | Absolute destination file path. Must be under your home directory, a temp directory, or `/Volumes`, and never inside the Notes data folder (`~/Library/Group Containers/group.com.apple.notes`), however it is reached (symlink or different letter case) |
1606
1606
 
1607
1607
  **Returns:** Confirmation with the saved path, name, and content type (also in `structuredContent`).
1608
1608
 
package/build/index.js CHANGED
@@ -39615,6 +39615,22 @@ import { createHash } from "node:crypto";
39615
39615
  import { homedir as homedir2 } from "node:os";
39616
39616
  import { join as join2 } from "node:path";
39617
39617
  import { gunzipSync as gunzipSync2 } from "node:zlib";
39618
+
39619
+ // src/utils/uniqueById.ts
39620
+ function uniqueById(items) {
39621
+ const seen = /* @__PURE__ */ new Set();
39622
+ const result = [];
39623
+ for (const item of items) {
39624
+ if (item.id) {
39625
+ if (seen.has(item.id)) continue;
39626
+ seen.add(item.id);
39627
+ }
39628
+ result.push(item);
39629
+ }
39630
+ return result;
39631
+ }
39632
+
39633
+ // src/utils/noteRichText.ts
39618
39634
  var HTML_LOSSY_ORDER = [
39619
39635
  "superscript",
39620
39636
  "subscript",
@@ -39706,14 +39722,15 @@ function parseRichNote(data, nativeTags = []) {
39706
39722
  hasNativeObjects ||= Boolean(getField(fields, 12));
39707
39723
  const attachment = embeddedMessage(getField(fields, 12));
39708
39724
  const attachmentId = attachment && stringValue(getField(attachment, 1));
39709
- if (attachmentId) nativeObjectIds.push(attachmentId);
39710
- if (attachmentId)
39725
+ if (attachment && attachmentId && !nativeObjectIds.includes(attachmentId)) {
39726
+ nativeObjectIds.push(attachmentId);
39711
39727
  objects.push({
39712
39728
  id: attachmentId,
39713
39729
  type: stringValue(getField(attachment, 2)) || "unknown",
39714
39730
  start: position,
39715
39731
  length
39716
39732
  });
39733
+ }
39717
39734
  hasChecklist ||= Boolean(paragraph && varintValue(getField(paragraph, 1)) === 103);
39718
39735
  if (paragraph && varintValue(getField(paragraph, 1)) === 103) {
39719
39736
  const checklist = embeddedMessage(getField(paragraph, 5));
@@ -39771,7 +39788,9 @@ function readRichNote(id2) {
39771
39788
  (row) => !row || typeof row.id !== "string" || !Number.isInteger(row.pk) || typeof row.mergeable !== "string" || !/^[0-9a-f]*$/i.test(row.mergeable)
39772
39789
  ))
39773
39790
  throw new Error("Invalid native object metadata");
39774
- rich.objectData = objectData.filter((row) => rich.nativeObjectIds.includes(row.id)).sort((a, b) => a.id.localeCompare(b.id));
39791
+ rich.objectData = uniqueById(
39792
+ objectData.filter((row) => rich.nativeObjectIds.includes(row.id)).sort((a, b) => a.id.localeCompare(b.id))
39793
+ );
39775
39794
  rich.revision = createHash("sha256").update(rich.revision).update(JSON.stringify(rich.objectData)).digest("hex");
39776
39795
  rich.nativeTagObjectIds = {};
39777
39796
  for (const id3 of rich.nativeObjectIds)
@@ -40942,6 +40961,9 @@ function allowedSaveRoots() {
40942
40961
  "/private/tmp"
40943
40962
  ];
40944
40963
  }
40964
+ function deniedSaveRoots() {
40965
+ return [join6(homedir6(), "Library/Group Containers/group.com.apple.notes")];
40966
+ }
40945
40967
  function canonicalize(path9) {
40946
40968
  return realpathSync.native(path9);
40947
40969
  }
@@ -40983,11 +41005,11 @@ function deepestExistingAncestor(abs) {
40983
41005
  current = parent;
40984
41006
  }
40985
41007
  }
40986
- function ensureParentDir(abs, roots = allowedSaveRoots()) {
40987
- assertSafeSavePath(abs, roots);
41008
+ function ensureParentDir(abs, roots = allowedSaveRoots(), denied = deniedSaveRoots()) {
41009
+ assertSafeSavePath(abs, roots, denied);
40988
41010
  mkdirSync(dirname(abs), { recursive: true });
40989
41011
  }
40990
- function assertSafeSavePath(p, roots = allowedSaveRoots()) {
41012
+ function assertSafeSavePath(p, roots = allowedSaveRoots(), denied = deniedSaveRoots()) {
40991
41013
  if (!p || !p.trim()) throw new Error("A destination path is required.");
40992
41014
  if (!isAbsolute(p)) throw new Error(`Destination path must be absolute: "${p}"`);
40993
41015
  const abs = resolve(p);
@@ -41015,6 +41037,12 @@ function assertSafeSavePath(p, roots = allowedSaveRoots()) {
41015
41037
  `Refusing to write outside allowed locations (home, temp, /Volumes): "${abs}" resolves to "${canonicalDest}" through a symbolic link.`
41016
41038
  );
41017
41039
  }
41040
+ const deniedForms = [
41041
+ .../* @__PURE__ */ new Set([...denied.map((d) => resolve(d)), ...canonicalRoots(denied)])
41042
+ ].map((d) => d.toLowerCase());
41043
+ if ([abs, canonicalDest].some((form) => isWithinRoots(form.toLowerCase(), deniedForms))) {
41044
+ throw new Error(`Refusing to write inside the Notes library container: "${abs}"`);
41045
+ }
41018
41046
  return abs;
41019
41047
  }
41020
41048
  var DEFAULT_MAX_ATTACHMENT_BYTES = 25 * 1024 * 1024;
@@ -43237,7 +43265,7 @@ var AppleNotesManager = class {
43237
43265
  }
43238
43266
  }
43239
43267
  }
43240
- return sharedNotes;
43268
+ return uniqueById(sharedNotes);
43241
43269
  }
43242
43270
  // ===========================================================================
43243
43271
  // Folder Operations
@@ -44076,7 +44104,7 @@ var AppleNotesManager = class {
44076
44104
  });
44077
44105
  }
44078
44106
  }
44079
- return attachments;
44107
+ return uniqueById(attachments);
44080
44108
  }
44081
44109
  /**
44082
44110
  * Lists attachments for a note by its title.
@@ -44163,7 +44191,7 @@ var AppleNotesManager = class {
44163
44191
  });
44164
44192
  }
44165
44193
  }
44166
- return attachments;
44194
+ return uniqueById(attachments);
44167
44195
  }
44168
44196
  /**
44169
44197
  * Saves a single attachment of a note (identified by attachment id) to a file
@@ -52745,13 +52773,15 @@ registerTool(
52745
52773
  registerTool(
52746
52774
  "save-attachment",
52747
52775
  {
52748
- description: "Use when: writing one note attachment to a file on disk.\nReturns: the saved path.\nDo not use when: you want the bytes in-memory as base64 (fetch-attachment).\nSafety: writes a file; savePath must be absolute and under the home directory, a temp dir, or /Volumes. Get the ids from list-attachments first.",
52776
+ description: "Use when: writing one note attachment to a file on disk.\nReturns: the saved path.\nDo not use when: you want the bytes in-memory as base64 (fetch-attachment).\nSafety: writes a file; savePath must be absolute and under the home directory, a temp dir, or /Volumes, and not inside the Notes data folder. Get the ids from list-attachments first.",
52749
52777
  inputSchema: {
52750
52778
  noteId: looseNoteId(external_exports.string().min(1, "noteId is required")).describe(
52751
52779
  `Note id (from search/list): ${NOTE_ID_FORMS}`
52752
52780
  ),
52753
52781
  attachmentId: external_exports.string().min(1, "attachmentId is required").max(MAX.ATTACHMENT_ID).describe("Attachment id (from list-attachments)"),
52754
- savePath: external_exports.string().min(1, "savePath is required").max(MAX.SAVE_PATH).describe("Absolute destination file path (must be under home, temp, or /Volumes)")
52782
+ savePath: external_exports.string().min(1, "savePath is required").max(MAX.SAVE_PATH).describe(
52783
+ "Absolute destination file path (must be under home, temp, or /Volumes; never inside the Notes data folder)"
52784
+ )
52755
52785
  },
52756
52786
  outputSchema: {
52757
52787
  savedPath: external_exports.string().optional(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-notes-mcp",
3
- "version": "2.8.40",
3
+ "version": "2.8.42",
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",