apple-notes-mcp 2.5.8 → 2.5.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.
Files changed (3) hide show
  1. package/README.md +1 -0
  2. package/build/index.js +102 -19
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -943,6 +943,7 @@ All configuration is optional — the server works out of the box. Override beha
943
943
  |----------|---------|-------------|
944
944
  | `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. |
945
945
  | `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. |
946
+ | `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. |
946
947
  | `APPLE_NOTES_MCP_CONFIG_FILE` | `~/Library/Application Support/apple-notes-mcp/config.json` | Path to the JSON config file (see below). |
947
948
  | `APPLE_NOTES_MCP_TIMEOUT_MS` | `30000` (30 s) | Per-call AppleScript timeout. Raise it if full-library operations (large searches, exports) time out on a big Notes library. Per-call `timeoutMs` options still win. |
948
949
  | `APPLE_NOTES_MCP_MAX_RETRIES` | `2` | Total attempts for an AppleScript call that fails with a **transient** error (Notes.app busy / not responding / lost connection / timeout). `2` means one retry; set `1` to fail fast with no retries. Non-transient errors (e.g. "note not found") never retry. |
package/build/index.js CHANGED
@@ -39140,11 +39140,21 @@ function getChecklistItems(noteId) {
39140
39140
  }
39141
39141
 
39142
39142
  // src/utils/attachmentFs.ts
39143
- import { existsSync as existsSync2, mkdtempSync, readFileSync, rmSync, statSync } from "fs";
39144
- import { isAbsolute, resolve, sep } from "path";
39143
+ import { existsSync as existsSync2, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync } from "fs";
39144
+ import { dirname, isAbsolute, resolve, sep } from "path";
39145
39145
  import { homedir as homedir2, tmpdir } from "os";
39146
39146
  function allowedSaveRoots() {
39147
- return [resolve(homedir2()), resolve(tmpdir()), "/Volumes", "/private/var/folders", "/tmp"];
39147
+ return [
39148
+ resolve(homedir2()),
39149
+ resolve(tmpdir()),
39150
+ "/Volumes",
39151
+ "/private/var/folders",
39152
+ "/tmp",
39153
+ "/private/tmp"
39154
+ ];
39155
+ }
39156
+ function ensureParentDir(abs) {
39157
+ mkdirSync(dirname(abs), { recursive: true });
39148
39158
  }
39149
39159
  function assertSafeSavePath(p, roots = allowedSaveRoots()) {
39150
39160
  if (!p || !p.trim()) throw new Error("A destination path is required.");
@@ -39282,6 +39292,10 @@ function buildAppleScriptDateVar(date3, varName = "thresholdDate") {
39282
39292
  function asDatePartsExpr(v) {
39283
39293
  return `((year of ${v}) as text) & "-" & ((month of ${v}) as integer as text) & "-" & ((day of ${v}) as text) & "-" & ((hours of ${v}) as text) & "-" & ((minutes of ${v}) as text) & "-" & ((seconds of ${v}) as text)`;
39284
39294
  }
39295
+ function normalizeAppleScriptText(field) {
39296
+ const trimmed = field?.trim();
39297
+ return trimmed && trimmed !== "missing value" ? trimmed : void 0;
39298
+ }
39285
39299
  function parseNotePropertiesOutput(output) {
39286
39300
  const parts = output.split(FIELD_SEP);
39287
39301
  if (parts.length < 6) {
@@ -40471,7 +40485,7 @@ var AppleNotesManager = class {
40471
40485
  end if
40472
40486
  end repeat
40473
40487
  if theAttachment is missing value then
40474
- return "ERR${AS_FIELD_SEP}attachment not found"
40488
+ return "ERR" & ${AS_FIELD_SEP} & "attachment not found"
40475
40489
  end if
40476
40490
  show theAttachment${separatelyClause}
40477
40491
  return "OK"
@@ -40737,8 +40751,8 @@ var AppleNotesManager = class {
40737
40751
  set end of attachmentList to attachId & ${AS_FIELD_SEP} & attachName & ${AS_FIELD_SEP} & attachContentId & ${AS_FIELD_SEP} & attachUrl & ${AS_FIELD_SEP} & createdParts & ${AS_FIELD_SEP} & modifiedParts & ${AS_FIELD_SEP} & sharedFlag
40738
40752
  end repeat
40739
40753
  set output to ""
40740
- repeat with item in attachmentList
40741
- set output to output & item & ${AS_RECORD_SEP}
40754
+ repeat with recordItem in attachmentList
40755
+ set output to output & recordItem & ${AS_RECORD_SEP}
40742
40756
  end repeat
40743
40757
  return output
40744
40758
  end tell
@@ -40760,7 +40774,7 @@ var AppleNotesManager = class {
40760
40774
  name: parts[1].trim(),
40761
40775
  contentType: parts[2].trim(),
40762
40776
  contentId: parts[2].trim() || void 0,
40763
- url: parts[3]?.trim() || void 0,
40777
+ url: normalizeAppleScriptText(parts[3]),
40764
40778
  created: parts[4] ? parseAppleScriptDate(parts[4].trim()) : void 0,
40765
40779
  modified: parts[5] ? parseAppleScriptDate(parts[5].trim()) : void 0,
40766
40780
  shared: parts[6] ? parts[6].trim().toLowerCase() === "true" : void 0
@@ -40801,8 +40815,8 @@ var AppleNotesManager = class {
40801
40815
  set end of attachmentList to attachId & ${AS_FIELD_SEP} & attachName & ${AS_FIELD_SEP} & attachContentId & ${AS_FIELD_SEP} & attachUrl & ${AS_FIELD_SEP} & createdParts & ${AS_FIELD_SEP} & modifiedParts & ${AS_FIELD_SEP} & sharedFlag
40802
40816
  end repeat
40803
40817
  set output to ""
40804
- repeat with item in attachmentList
40805
- set output to output & item & ${AS_RECORD_SEP}
40818
+ repeat with recordItem in attachmentList
40819
+ set output to output & recordItem & ${AS_RECORD_SEP}
40806
40820
  end repeat
40807
40821
  return output
40808
40822
  end tell
@@ -40825,7 +40839,7 @@ var AppleNotesManager = class {
40825
40839
  name: parts[1].trim(),
40826
40840
  contentType: parts[2].trim(),
40827
40841
  contentId: parts[2].trim() || void 0,
40828
- url: parts[3]?.trim() || void 0,
40842
+ url: normalizeAppleScriptText(parts[3]),
40829
40843
  created: parts[4] ? parseAppleScriptDate(parts[4].trim()) : void 0,
40830
40844
  modified: parts[5] ? parseAppleScriptDate(parts[5].trim()) : void 0,
40831
40845
  shared: parts[6] ? parts[6].trim().toLowerCase() === "true" : void 0
@@ -40847,6 +40861,7 @@ var AppleNotesManager = class {
40847
40861
  let abs;
40848
40862
  try {
40849
40863
  abs = assertSafeSavePath(savePath);
40864
+ ensureParentDir(abs);
40850
40865
  } catch (e) {
40851
40866
  return { success: false, error: e instanceof Error ? e.message : String(e) };
40852
40867
  }
@@ -40864,10 +40879,18 @@ var AppleNotesManager = class {
40864
40879
  end if
40865
40880
  end repeat
40866
40881
  if theAttachment is missing value then
40867
- return "ERR${AS_FIELD_SEP}attachment not found"
40882
+ return "ERR" & ${AS_FIELD_SEP} & "attachment not found"
40868
40883
  end if
40869
- save theAttachment in (POSIX file "${safePath}")
40870
- return "OK${AS_FIELD_SEP}" & (name of theAttachment) & "${AS_FIELD_SEP}" & (content identifier of theAttachment)
40884
+ set attachUrl to ""
40885
+ try
40886
+ set attachUrl to URL of theAttachment as text
40887
+ end try
40888
+ try
40889
+ save theAttachment in (POSIX file "${safePath}")
40890
+ on error errMsg
40891
+ return "ERRSAVE" & ${AS_FIELD_SEP} & errMsg & ${AS_FIELD_SEP} & attachUrl
40892
+ end try
40893
+ return "OK" & ${AS_FIELD_SEP} & (name of theAttachment) & ${AS_FIELD_SEP} & (content identifier of theAttachment)
40871
40894
  end tell
40872
40895
  `;
40873
40896
  const result = executeAppleScript(script);
@@ -40875,6 +40898,16 @@ var AppleNotesManager = class {
40875
40898
  return { success: false, error: result.error ?? "unknown error" };
40876
40899
  }
40877
40900
  const parts = (result.output ?? "").trim().split(FIELD_SEP);
40901
+ if (parts[0] === "ERRSAVE") {
40902
+ const saveErr = (parts[1]?.trim() || "unknown error").replace(/\.$/, "");
40903
+ const rawUrl = parts[2]?.trim();
40904
+ const attachUrl = rawUrl && rawUrl !== "missing value" ? rawUrl : void 0;
40905
+ const linkHint = attachUrl ? ` This attachment appears to be a link preview (URL: ${attachUrl}) rather than a file, and link previews have no file payload to save.` : "";
40906
+ return {
40907
+ success: false,
40908
+ error: `Notes could not save this attachment: ${saveErr}.${linkHint}`
40909
+ };
40910
+ }
40878
40911
  if (parts[0] !== "OK") {
40879
40912
  return { success: false, error: parts[1]?.trim() || "attachment not found" };
40880
40913
  }
@@ -41552,6 +41585,46 @@ function parseHashtags(body) {
41552
41585
  return result;
41553
41586
  }
41554
41587
 
41588
+ // src/utils/inlineImages.ts
41589
+ var DEFAULT_MAX_INLINE_IMAGE_BYTES = 256 * 1024;
41590
+ function maxInlineImageBytes(env = process.env) {
41591
+ const raw = env.APPLE_NOTES_MCP_MAX_INLINE_IMAGE_BYTES;
41592
+ if (raw !== void 0) {
41593
+ const n = Number(raw);
41594
+ if (Number.isFinite(n) && n > 0) return n;
41595
+ }
41596
+ return DEFAULT_MAX_INLINE_IMAGE_BYTES;
41597
+ }
41598
+ function formatBytes(bytes) {
41599
+ if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
41600
+ if (bytes >= 1024) return `${Math.round(bytes / 1024)} KB`;
41601
+ return `${bytes} B`;
41602
+ }
41603
+ var INLINE_IMG_RE = /<img\b[^>]*\bsrc\s*=\s*(["'])data:([^;'"]+);base64,([^"']*)\1[^>]*\/?>/gi;
41604
+ function stripLargeInlineImages(html, maxBytes = maxInlineImageBytes()) {
41605
+ let strippedCount = 0;
41606
+ let strippedBytes = 0;
41607
+ const result = html.replace(INLINE_IMG_RE, (tag, _quote, mediaType, b64) => {
41608
+ if (b64.length <= maxBytes) return tag;
41609
+ const decodedBytes = Math.floor(b64.length * 3 / 4);
41610
+ strippedCount += 1;
41611
+ strippedBytes += decodedBytes;
41612
+ return `<div>[inline image omitted: ${mediaType}, ~${formatBytes(
41613
+ decodedBytes
41614
+ )}; use list-attachments and save-attachment or fetch-attachment to export it]</div>`;
41615
+ });
41616
+ return { html: result, strippedCount, strippedBytes };
41617
+ }
41618
+ function strippedImagesWarning(stripped) {
41619
+ if (stripped.strippedCount === 0) return null;
41620
+ const plural = stripped.strippedCount === 1 ? "image" : "images";
41621
+ return `
41622
+
41623
+ \u26A0\uFE0F ${stripped.strippedCount} inline ${plural} (~${formatBytes(
41624
+ stripped.strippedBytes
41625
+ )} decoded) exceeded the per-image inline cap and ${stripped.strippedCount === 1 ? "was" : "were"} replaced with placeholders so the response stays within MCP message limits. The images are still in the note: use list-attachments with save-attachment or fetch-attachment to export them, or raise APPLE_NOTES_MCP_MAX_INLINE_IMAGE_BYTES.`;
41626
+ }
41627
+
41555
41628
  // src/tools/doctor.ts
41556
41629
  import { spawnSync as spawnSync2 } from "child_process";
41557
41630
  function runDoctor(manager) {
@@ -41911,12 +41984,19 @@ server.registerTool(
41911
41984
  `Note "${note2.title}" is password-protected and cannot be read. Unlock it in Notes.app first.`
41912
41985
  );
41913
41986
  }
41914
- const content2 = notesManager.getNoteContentById(id);
41915
- if (!content2) {
41987
+ const rawContent2 = notesManager.getNoteContentById(id);
41988
+ if (!rawContent2) {
41916
41989
  return errorResponse(`Failed to read content of note "${note2.title}"`);
41917
41990
  }
41991
+ const stripped2 = stripLargeInlineImages(rawContent2);
41992
+ const content2 = stripped2.html;
41918
41993
  const hashtags2 = parseHashtags(content2);
41919
- return successResponse(content2, { title: note2.title, content: content2, hashtags: hashtags2 });
41994
+ const warning2 = strippedImagesWarning(stripped2);
41995
+ return successResponse(warning2 ? content2 + warning2 : content2, {
41996
+ title: note2.title,
41997
+ content: content2,
41998
+ hashtags: hashtags2
41999
+ });
41920
42000
  }
41921
42001
  if (!title) {
41922
42002
  return errorResponse("Either 'id' or 'title' is required");
@@ -41930,12 +42010,15 @@ server.registerTool(
41930
42010
  `Note "${title}" is password-protected and cannot be read. Unlock it in Notes.app first.`
41931
42011
  );
41932
42012
  }
41933
- const content = notesManager.getNoteContent(title, account);
41934
- if (!content) {
42013
+ const rawContent = notesManager.getNoteContent(title, account);
42014
+ if (!rawContent) {
41935
42015
  return errorResponse(`Failed to read content of note "${title}"`);
41936
42016
  }
42017
+ const stripped = stripLargeInlineImages(rawContent);
42018
+ const content = stripped.html;
41937
42019
  const hashtags = parseHashtags(content);
41938
- return successResponse(content, { title, content, hashtags });
42020
+ const warning = strippedImagesWarning(stripped);
42021
+ return successResponse(warning ? content + warning : content, { title, content, hashtags });
41939
42022
  }, "Error retrieving note content")
41940
42023
  );
41941
42024
  server.registerTool(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-notes-mcp",
3
- "version": "2.5.8",
3
+ "version": "2.5.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",