apple-notes-mcp 2.9.8 → 2.9.9

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
@@ -1873,6 +1873,14 @@ fetched attachment bytes with the source before reporting success.
1873
1873
  that exact name. A mismatch is a warning (`filenameWarning`), not a failure,
1874
1874
  because the attachment and its bytes are already verified.
1875
1875
 
1876
+ On macOS 27, Notes' AppleScript does not list PDF attachments, so it cannot see
1877
+ a PDF this tool just added. When that happens and the server has Full Disk
1878
+ Access, it verifies through the read-only NoteStore database instead: success
1879
+ requires exactly one new attachment row on the note whose media file matches
1880
+ the source bytes, and the result carries `verifiedBy: "database"`. Without Full
1881
+ Disk Access a PDF attach reports "insertion outcome uncertain"; read the note
1882
+ before retrying, because the attachment was probably created.
1883
+
1876
1884
  ---
1877
1885
 
1878
1886
  #### `create-note-with-attachment`
package/build/index.js CHANGED
@@ -51463,7 +51463,7 @@ function registerDirectOperations(server2, manager) {
51463
51463
  };
51464
51464
  tool(
51465
51465
  "add-attachment",
51466
- "Use when: adding one local file to an exact note without replacing its body.\nReturns: the new attachment id, byte count, attachment name, and post-write content hash after exact byte verification.\nDo not use when: reading or exporting an existing attachment.\nSafety: requires a fresh rich revision, copies at most 64 MiB through a private temporary file, never retries insertion, and verifies existing content plus fetched bytes.",
51466
+ "Use when: adding one local file to an exact note without replacing its body.\nReturns: the new attachment id, byte count, attachment name, and post-write content hash after exact byte verification.\nDo not use when: reading or exporting an existing attachment.\nSafety: requires a fresh rich revision, copies at most 64 MiB through a private temporary file, never retries insertion, and verifies existing content plus fetched bytes. On macOS 27 Notes' AppleScript does not list PDF attachments; with Full Disk Access the new attachment is verified through the read-only NoteStore database instead (verifiedBy: database), otherwise the outcome is reported as uncertain.",
51467
51467
  { id: noteId, expectedContentHash: revision, ...attachmentInput },
51468
51468
  (args) => attachFile(manager, args)
51469
51469
  );
@@ -51532,6 +51532,56 @@ function attachmentName(path10, filename) {
51532
51532
  );
51533
51533
  return filename;
51534
51534
  }
51535
+ var UNCERTAIN = "Attachment insertion outcome uncertain; read the exact note before retrying";
51536
+ var pause = (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
51537
+ var sha256 = (data) => createHash2("sha256").update(data).digest("hex");
51538
+ function storedAttachmentIds(manager, id2) {
51539
+ try {
51540
+ return new Set(
51541
+ manager.getAttachmentAssetsById(id2).attachments.filter((item) => item.parentIdentifier === null).map((item) => item.identifier.toLowerCase())
51542
+ );
51543
+ } catch {
51544
+ return null;
51545
+ }
51546
+ }
51547
+ function storedInsertion(manager, id2, before, bytes, returnedId) {
51548
+ let added = [];
51549
+ for (let attempt = 0; attempt < 8; attempt++) {
51550
+ if (attempt > 0) pause(250);
51551
+ try {
51552
+ added = manager.getAttachmentAssetsById(id2).attachments.filter(
51553
+ (item) => item.parentIdentifier === null && !before.has(item.identifier.toLowerCase())
51554
+ );
51555
+ } catch {
51556
+ added = [];
51557
+ }
51558
+ if (added.length === 1 && added[0].assetPaths.length > 0) break;
51559
+ }
51560
+ if (added.length === 0) return null;
51561
+ if (added.length > 1) throw new Error(UNCERTAIN);
51562
+ const row = added[0];
51563
+ const attachmentId = attachmentCoreDataId(id2, row.pk);
51564
+ if (returnedId && /\/ICAttachment\/p\d+$/.test(returnedId) && returnedId !== attachmentId)
51565
+ throw new Error(UNCERTAIN);
51566
+ const expected = sha256(bytes);
51567
+ const matches = row.assetPaths.some((path10) => {
51568
+ let descriptor;
51569
+ try {
51570
+ descriptor = openSync5(path10, constants5.O_RDONLY | constants5.O_NOFOLLOW);
51571
+ const stat = fstatSync5(descriptor);
51572
+ return stat.isFile() && stat.size === bytes.length && sha256(readFileSync3(descriptor)) === expected;
51573
+ } catch {
51574
+ return false;
51575
+ } finally {
51576
+ if (descriptor !== void 0) closeSync5(descriptor);
51577
+ }
51578
+ });
51579
+ if (!matches)
51580
+ throw new Error(
51581
+ `Notes' database shows new attachment ${attachmentId} on this note, but its file bytes could not be verified; read the exact note and do not attach the file again`
51582
+ );
51583
+ return { attachmentId, name: row.filename };
51584
+ }
51535
51585
  function attachFile(manager, args) {
51536
51586
  const { id: id2, expectedContentHash, path: path10 } = args;
51537
51587
  const name = attachmentName(path10, args.filename);
@@ -51539,6 +51589,7 @@ function attachFile(manager, args) {
51539
51589
  if (before.hash !== expectedContentHash) throw new Error("Note revision changed");
51540
51590
  const bytes = localAttachment(path10);
51541
51591
  const beforeAttachments = manager.listAttachmentsById(id2);
51592
+ const beforeStored = storedAttachmentIds(manager, id2);
51542
51593
  const directory = mkdtempSync4(join22(tmpdir4(), "notes-attachment-add-"));
51543
51594
  const temporaryFile = join22(directory, name);
51544
51595
  try {
@@ -51558,27 +51609,39 @@ function attachFile(manager, args) {
51558
51609
  );
51559
51610
  let inserted = readInserted();
51560
51611
  for (let attempt = 0; attempt < 4 && (inserted.length !== 1 || returnedId && returnedId !== inserted[0].id); attempt++) {
51561
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 250);
51612
+ pause(250);
51562
51613
  inserted = readInserted();
51563
51614
  }
51564
51615
  const persistentReturnedId = returnedId && /\/ICAttachment\/p\d+$/.test(returnedId);
51565
- if (inserted.length !== 1 || persistentReturnedId && returnedId !== inserted[0].id)
51566
- throw new Error(
51567
- "Attachment insertion outcome uncertain; read the exact note before retrying"
51568
- );
51569
- const attachmentId = inserted[0].id;
51570
- const fetched = manager.getAttachmentBase64ById(id2, attachmentId);
51571
- const actual = typeof fetched.base64 === "string" ? Buffer.from(fetched.base64, "base64") : null;
51572
- if (!actual || createHash2("sha256").update(actual).digest("hex") !== createHash2("sha256").update(bytes).digest("hex"))
51573
- throw new Error("Attachment bytes were not verified; read the exact note before retrying");
51574
- const nameVerified = inserted[0].name === name;
51616
+ let attachmentId;
51617
+ let reportedName;
51618
+ let verifiedBy = "applescript";
51619
+ if (inserted.length === 1 && !(persistentReturnedId && returnedId !== inserted[0].id)) {
51620
+ attachmentId = inserted[0].id;
51621
+ reportedName = inserted[0].name;
51622
+ const fetched = manager.getAttachmentBase64ById(id2, attachmentId);
51623
+ const actual = typeof fetched.base64 === "string" ? Buffer.from(fetched.base64, "base64") : null;
51624
+ if (!actual || sha256(actual) !== sha256(bytes))
51625
+ throw new Error("Attachment bytes were not verified; read the exact note before retrying");
51626
+ } else {
51627
+ const stored = inserted.length === 0 && beforeStored ? storedInsertion(manager, id2, beforeStored, bytes, returnedId) : null;
51628
+ if (!stored)
51629
+ throw new Error(
51630
+ inserted.length === 0 && !beforeStored ? `${UNCERTAIN}. Notes' AppleScript does not list some attachments (PDFs on macOS 27); grant Full Disk Access so the server can verify through the Notes database` : UNCERTAIN
51631
+ );
51632
+ attachmentId = stored.attachmentId;
51633
+ reportedName = stored.name;
51634
+ verifiedBy = "database";
51635
+ }
51636
+ const nameVerified = reportedName === name;
51575
51637
  return {
51576
51638
  ok: true,
51577
51639
  id: id2,
51578
51640
  attachmentId,
51579
51641
  contentHash: after.hash,
51580
51642
  bytes: bytes.length,
51581
- name: inserted[0].name,
51643
+ name: reportedName,
51644
+ ...verifiedBy === "database" ? { verifiedBy } : {},
51582
51645
  ...args.filename === void 0 ? {} : {
51583
51646
  filenameVerified: nameVerified,
51584
51647
  ...nameVerified ? {} : {
@@ -53000,7 +53063,7 @@ function canonicalJson(value) {
53000
53063
  return `{${Object.keys(value).sort().map((k) => `${JSON.stringify(k)}:${canonicalJson(value[k])}`).join(",")}}`;
53001
53064
  return JSON.stringify(value);
53002
53065
  }
53003
- function sha256(text2) {
53066
+ function sha2562(text2) {
53004
53067
  return createHash4("sha256").update(text2).digest("hex");
53005
53068
  }
53006
53069
  var round = (v, digits) => {
@@ -53840,7 +53903,7 @@ function analysisFor(source, analyzer, viewport) {
53840
53903
  const classification = !importable || requiredLosses.includes("drop-content") ? "unsupported" : requiredLosses.length ? "lossy" : "safe";
53841
53904
  const analysis = {
53842
53905
  analyzer: SVG_ANALYZER_VERSION,
53843
- source: { sha256: sha256(source), bytes: source.length },
53906
+ source: { sha256: sha2562(source), bytes: source.length },
53844
53907
  classification,
53845
53908
  importable,
53846
53909
  defaultWriteAllowed: importable && classification === "safe",
@@ -53861,7 +53924,7 @@ function analysisFor(source, analyzer, viewport) {
53861
53924
  issues: analyzer.issues,
53862
53925
  issuesTruncated: analyzer.issuesTruncated
53863
53926
  };
53864
- const analysisDigest = "sha256:" + sha256(canonicalJson({ analysis, drawing }));
53927
+ const analysisDigest = "sha256:" + sha2562(canonicalJson({ analysis, drawing }));
53865
53928
  return { analysis: { ...analysis, analysisDigest }, drawing };
53866
53929
  }
53867
53930
  function analyzeSvgBuffer(source) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-notes-mcp",
3
- "version": "2.9.8",
3
+ "version": "2.9.9",
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",