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.
- package/README.md +1 -0
- package/build/index.js +102 -19
- 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 [
|
|
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
|
|
40741
|
-
set output to output &
|
|
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]
|
|
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
|
|
40805
|
-
set output to output &
|
|
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]
|
|
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
|
-
|
|
40870
|
-
|
|
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
|
|
41915
|
-
if (!
|
|
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
|
-
|
|
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
|
|
41934
|
-
if (!
|
|
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
|
-
|
|
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