apple-notes-mcp 2.8.39 → 2.8.41

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
@@ -555,6 +555,11 @@ changed after this read. With Full Disk Access, embedded URLs omitted by
555
555
  AppleScript are restored and returned in `links`. The response also reports
556
556
  actual `nativeTags`, `richContentComplete`, and `writable`. Textual `hashtags`
557
557
  remain a separate field and are not proof that Notes registered native tags.
558
+ `writable` is also false when the note uses formatting that AppleScript's HTML
559
+ does not carry (superscript, subscript, non-left paragraph alignment, or
560
+ highlight): a full-body `update-note` would silently drop it, so it is refused
561
+ and the `warning` names the formatting. `append-to-note` with `scopeText` still
562
+ works through the native path, which verifies existing formatting.
558
563
 
559
564
  **⚠️ The returned body can be lossy — do not write it back verbatim.** Inline
560
565
  base64 images larger than `APPLE_NOTES_MCP_MAX_INLINE_IMAGE_BYTES` (default
package/build/index.js CHANGED
@@ -39275,21 +39275,13 @@ var WIRE_TYPE = {
39275
39275
  };
39276
39276
  function decodeVarint(buf, offset) {
39277
39277
  let result = 0;
39278
- let shift = 0;
39279
- let pos = offset;
39280
- while (pos < buf.length) {
39281
- const byte = buf[pos];
39278
+ for (let pos = offset, shift = 0; pos < buf.length && shift < 28; shift += 7) {
39279
+ const byte = buf[pos++];
39282
39280
  result |= (byte & 127) << shift;
39283
- pos++;
39284
- if ((byte & 128) === 0) {
39285
- return [result, pos];
39286
- }
39287
- shift += 7;
39288
- if (shift > 35) {
39289
- throw new Error(`Varint too long at offset ${offset}`);
39290
- }
39281
+ if ((byte & 128) === 0) return [result, pos];
39291
39282
  }
39292
- throw new Error(`Unexpected end of buffer reading varint at offset ${offset}`);
39283
+ const [value, next] = decodeVarint64(buf, offset);
39284
+ return [Number(BigInt.asIntN(64, value)), next];
39293
39285
  }
39294
39286
  function decodeMessage(buf, options = {}) {
39295
39287
  const fields = [];
@@ -39306,7 +39298,7 @@ function decodeMessage(buf, options = {}) {
39306
39298
  } else if (wireType === WIRE_TYPE.LENGTH_DELIMITED) {
39307
39299
  let length;
39308
39300
  [length, offset] = decodeVarint(buf, offset);
39309
- if (offset + length > buf.length) {
39301
+ if (length < 0 || offset + length > buf.length) {
39310
39302
  break;
39311
39303
  }
39312
39304
  const value = buf.slice(offset, offset + length);
@@ -39623,6 +39615,28 @@ import { createHash } from "node:crypto";
39623
39615
  import { homedir as homedir2 } from "node:os";
39624
39616
  import { join as join2 } from "node:path";
39625
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
39634
+ var HTML_LOSSY_ORDER = [
39635
+ "superscript",
39636
+ "subscript",
39637
+ "alignment",
39638
+ "highlight"
39639
+ ];
39626
39640
  var dbPath = join2(homedir2(), "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite");
39627
39641
  var safeUrl = (url) => /^(?:https?:\/\/|notes:\/\/|applenotes:|mailto:)/i.test(url) && !Array.from(url).some((char) => char.charCodeAt(0) < 32);
39628
39642
  var escapeAttribute = (text2) => text2.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
@@ -39643,6 +39657,7 @@ function styleValue(field) {
39643
39657
  else if (wire === 5) offset += 4;
39644
39658
  else if (wire === 2) {
39645
39659
  [length, offset] = decodeVarint(data, offset);
39660
+ if (length < 0) return Buffer.from(data).toString("hex");
39646
39661
  offset += length;
39647
39662
  } else return Buffer.from(data).toString("hex");
39648
39663
  if (offset > data.length) return Buffer.from(data).toString("hex");
@@ -39669,6 +39684,7 @@ function parseRichNote(data, nativeTags = []) {
39669
39684
  let position = 0;
39670
39685
  let hasNativeObjects = false;
39671
39686
  let hasChecklist = false;
39687
+ const lossy = /* @__PURE__ */ new Set();
39672
39688
  for (const run of getFields(body, 5)) {
39673
39689
  const fields = embeddedMessage(run);
39674
39690
  if (!fields) throw new Error("Invalid Notes attribute run");
@@ -39686,6 +39702,13 @@ function parseRichNote(data, nativeTags = []) {
39686
39702
  fields.filter((f) => f.fieldNumber >= 2 && f.fieldNumber <= 12 || f.fieldNumber === 14).map((f) => [f.fieldNumber, styleValue(f)])
39687
39703
  )
39688
39704
  });
39705
+ if (/[^\s\ufffc]/u.test(text2.slice(position, position + length))) {
39706
+ const baseline = varintValue(getField(fields, 8)) ?? 0;
39707
+ if (baseline > 0) lossy.add("superscript");
39708
+ if (baseline < 0) lossy.add("subscript");
39709
+ if (paragraph && (varintValue(getField(paragraph, 2)) ?? 0) !== 0) lossy.add("alignment");
39710
+ if (varintValue(getField(fields, 14))) lossy.add("highlight");
39711
+ }
39689
39712
  const url = stringValue(getField(fields, 9));
39690
39713
  if (url) {
39691
39714
  if (!safeUrl(url)) throw new Error("Unsupported link scheme in note");
@@ -39699,14 +39722,15 @@ function parseRichNote(data, nativeTags = []) {
39699
39722
  hasNativeObjects ||= Boolean(getField(fields, 12));
39700
39723
  const attachment = embeddedMessage(getField(fields, 12));
39701
39724
  const attachmentId = attachment && stringValue(getField(attachment, 1));
39702
- if (attachmentId) nativeObjectIds.push(attachmentId);
39703
- if (attachmentId)
39725
+ if (attachment && attachmentId && !nativeObjectIds.includes(attachmentId)) {
39726
+ nativeObjectIds.push(attachmentId);
39704
39727
  objects.push({
39705
39728
  id: attachmentId,
39706
39729
  type: stringValue(getField(attachment, 2)) || "unknown",
39707
39730
  start: position,
39708
39731
  length
39709
39732
  });
39733
+ }
39710
39734
  hasChecklist ||= Boolean(paragraph && varintValue(getField(paragraph, 1)) === 103);
39711
39735
  if (paragraph && varintValue(getField(paragraph, 1)) === 103) {
39712
39736
  const checklist = embeddedMessage(getField(paragraph, 5));
@@ -39737,7 +39761,8 @@ function parseRichNote(data, nativeTags = []) {
39737
39761
  revision: createHash("sha256").update(data).digest("hex"),
39738
39762
  objects,
39739
39763
  checklistItems,
39740
- styleRuns
39764
+ styleRuns,
39765
+ ...lossy.size ? { htmlLossyFormatting: HTML_LOSSY_ORDER.filter((f) => lossy.has(f)) } : {}
39741
39766
  };
39742
39767
  }
39743
39768
  function readRichNote(id2) {
@@ -39763,7 +39788,9 @@ function readRichNote(id2) {
39763
39788
  (row) => !row || typeof row.id !== "string" || !Number.isInteger(row.pk) || typeof row.mergeable !== "string" || !/^[0-9a-f]*$/i.test(row.mergeable)
39764
39789
  ))
39765
39790
  throw new Error("Invalid native object metadata");
39766
- 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
+ );
39767
39794
  rich.revision = createHash("sha256").update(rich.revision).update(JSON.stringify(rich.objectData)).digest("hex");
39768
39795
  rich.nativeTagObjectIds = {};
39769
39796
  for (const id3 of rich.nativeObjectIds)
@@ -39854,17 +39881,26 @@ function enrichNoteRead(id2, rawBody) {
39854
39881
  const rich = readRichNote(id2);
39855
39882
  metadata = rich;
39856
39883
  const content = restoreNoteLinks(rawBody, rich);
39857
- const writable = !rich.hasNativeObjects && !rich.hasChecklist;
39884
+ const complete = !rich.hasNativeObjects && !rich.hasChecklist;
39885
+ const lossy = rich.htmlLossyFormatting ?? [];
39886
+ const writable = complete && lossy.length === 0;
39887
+ const warnings = [];
39888
+ if (!complete)
39889
+ warnings.push(
39890
+ "Native tags, inline objects or checklists are present. Their state is not writable through AppleScript; full-body edits are blocked to preserve them."
39891
+ );
39892
+ if (lossy.length)
39893
+ warnings.push(
39894
+ `This note uses formatting that Notes' AppleScript HTML does not carry (${lossy.join(", ")}); a full-body edit would silently drop it, so full-body edits are blocked. Edit it in Notes.app, or use append-to-note with scopeText, which appends natively and verifies existing formatting.`
39895
+ );
39858
39896
  return {
39859
39897
  content,
39860
39898
  links: rich.links,
39861
39899
  nativeTags: rich.nativeTags,
39862
- complete: writable,
39900
+ complete,
39863
39901
  writable,
39864
39902
  revision: rich.revision,
39865
- ...!writable ? {
39866
- warning: "Native tags, inline objects or checklists are present. Their state is not writable through AppleScript; full-body edits are blocked to preserve them."
39867
- } : {}
39903
+ ...warnings.length ? { warning: warnings.join(" ") } : {}
39868
39904
  };
39869
39905
  } catch (err) {
39870
39906
  const detail = err instanceof Error ? err.message : String(err);
@@ -43220,7 +43256,7 @@ var AppleNotesManager = class {
43220
43256
  }
43221
43257
  }
43222
43258
  }
43223
- return sharedNotes;
43259
+ return uniqueById(sharedNotes);
43224
43260
  }
43225
43261
  // ===========================================================================
43226
43262
  // Folder Operations
@@ -44059,7 +44095,7 @@ var AppleNotesManager = class {
44059
44095
  });
44060
44096
  }
44061
44097
  }
44062
- return attachments;
44098
+ return uniqueById(attachments);
44063
44099
  }
44064
44100
  /**
44065
44101
  * Lists attachments for a note by its title.
@@ -44146,7 +44182,7 @@ var AppleNotesManager = class {
44146
44182
  });
44147
44183
  }
44148
44184
  }
44149
- return attachments;
44185
+ return uniqueById(attachments);
44150
44186
  }
44151
44187
  /**
44152
44188
  * Saves a single attachment of a note (identified by attachment id) to a file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-notes-mcp",
3
- "version": "2.8.39",
3
+ "version": "2.8.41",
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",