apple-notes-mcp 2.15.16 → 2.15.18

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
@@ -2444,6 +2444,13 @@ after it finishes. The result carries `verifiedBy: "database"`. Without Full
2444
2444
  Disk Access a PDF attach reports "insertion outcome uncertain"; read the note
2445
2445
  before retrying, because the attachment was probably created.
2446
2446
 
2447
+ Notes.app's AppleScript insertion places every new attachment in the note body
2448
+ twice (#321): one attachment, shown two times. The tool removes the extra copy
2449
+ in the same AppleScript call and checks that the note lists the attachment once.
2450
+ A PDF on macOS 27 cannot be listed, so its extra copy cannot be removed; when the
2451
+ database shows the PDF twice, the tool returns an error naming the attachment.
2452
+ Delete the extra copy in Notes.app and do not attach the file again.
2453
+
2447
2454
  ---
2448
2455
 
2449
2456
  #### `create-note-with-attachment`
package/build/index.js CHANGED
@@ -45558,7 +45558,7 @@ function visit(schema, fnOrHandlers) {
45558
45558
  return run(schema);
45559
45559
  }
45560
45560
 
45561
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
45561
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.2_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
45562
45562
  function isZ4Schema(s) {
45563
45563
  const schema = s;
45564
45564
  return !!schema._zod;
@@ -48573,7 +48573,7 @@ function date4(params) {
48573
48573
  return _coercedDate(ZodDate2, params);
48574
48574
  }
48575
48575
 
48576
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js
48576
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.2_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js
48577
48577
  var LATEST_PROTOCOL_VERSION = "2025-11-25";
48578
48578
  var SUPPORTED_PROTOCOL_VERSIONS = [LATEST_PROTOCOL_VERSION, "2025-06-18", "2025-03-26", "2024-11-05", "2024-10-07"];
48579
48579
  var RELATED_TASK_META_KEY = "io.modelcontextprotocol/related-task";
@@ -50104,7 +50104,7 @@ var UrlElicitationRequiredError = class extends McpError {
50104
50104
  }
50105
50105
  };
50106
50106
 
50107
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.js
50107
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.2_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.js
50108
50108
  function isTerminal(status) {
50109
50109
  return status === "completed" || status === "failed" || status === "cancelled";
50110
50110
  }
@@ -51393,7 +51393,7 @@ var zodToJsonSchema = (schema, options) => {
51393
51393
  return combined;
51394
51394
  };
51395
51395
 
51396
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-json-schema-compat.js
51396
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.2_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-json-schema-compat.js
51397
51397
  function mapMiniTarget(t) {
51398
51398
  if (!t)
51399
51399
  return "draft-7";
@@ -51435,7 +51435,7 @@ function parseWithCompat(schema, data) {
51435
51435
  return result.data;
51436
51436
  }
51437
51437
 
51438
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
51438
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.2_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
51439
51439
  var DEFAULT_REQUEST_TIMEOUT_MSEC = 6e4;
51440
51440
  var Protocol = class {
51441
51441
  constructor(_options) {
@@ -52412,7 +52412,7 @@ function mergeCapabilities(base, additional) {
52412
52412
  return result;
52413
52413
  }
52414
52414
 
52415
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/validation/ajv-provider.js
52415
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.2_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/validation/ajv-provider.js
52416
52416
  var import_ajv = __toESM(require_ajv(), 1);
52417
52417
  var import_ajv_formats = __toESM(require_dist(), 1);
52418
52418
  function createDefaultAjvInstance() {
@@ -52480,7 +52480,7 @@ var AjvJsonSchemaValidator = class {
52480
52480
  }
52481
52481
  };
52482
52482
 
52483
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/server.js
52483
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.2_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/server.js
52484
52484
  var ExperimentalServerTasks = class {
52485
52485
  constructor(_server) {
52486
52486
  this._server = _server;
@@ -52693,7 +52693,7 @@ var ExperimentalServerTasks = class {
52693
52693
  }
52694
52694
  };
52695
52695
 
52696
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/helpers.js
52696
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.2_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/helpers.js
52697
52697
  function assertToolsCallTaskCapability(requests, method, entityName) {
52698
52698
  if (!requests) {
52699
52699
  throw new Error(`${entityName} does not support task creation (required for ${method})`);
@@ -52728,7 +52728,7 @@ function assertClientRequestTaskCapability(requests, method, entityName) {
52728
52728
  }
52729
52729
  }
52730
52730
 
52731
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js
52731
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.2_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js
52732
52732
  var Server = class extends Protocol {
52733
52733
  /**
52734
52734
  * Initializes this server with the given name and version information.
@@ -53099,7 +53099,7 @@ var Server = class extends Protocol {
53099
53099
  }
53100
53100
  };
53101
53101
 
53102
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/server/completable.js
53102
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.2_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/server/completable.js
53103
53103
  var COMPLETABLE_SYMBOL = /* @__PURE__ */ Symbol.for("mcp.completable");
53104
53104
  function isCompletable(schema) {
53105
53105
  return !!schema && typeof schema === "object" && COMPLETABLE_SYMBOL in schema;
@@ -53113,7 +53113,7 @@ var McpZodTypeKind;
53113
53113
  McpZodTypeKind2["Completable"] = "McpCompletable";
53114
53114
  })(McpZodTypeKind || (McpZodTypeKind = {}));
53115
53115
 
53116
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/uriTemplate.js
53116
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.2_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/uriTemplate.js
53117
53117
  var MAX_TEMPLATE_LENGTH = 1e6;
53118
53118
  var MAX_VARIABLE_LENGTH = 1e6;
53119
53119
  var MAX_TEMPLATE_EXPRESSIONS = 1e4;
@@ -53335,7 +53335,7 @@ var UriTemplate = class _UriTemplate {
53335
53335
  }
53336
53336
  };
53337
53337
 
53338
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/toolNameValidation.js
53338
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.2_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/toolNameValidation.js
53339
53339
  var TOOL_NAME_REGEX = /^[A-Za-z0-9._-]{1,128}$/;
53340
53340
  function validateToolName(name) {
53341
53341
  const warnings = [];
@@ -53393,7 +53393,7 @@ function validateAndWarnToolName(name) {
53393
53393
  return result.isValid;
53394
53394
  }
53395
53395
 
53396
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/mcp-server.js
53396
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.2_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/mcp-server.js
53397
53397
  var ExperimentalMcpServerTasks = class {
53398
53398
  constructor(_mcpServer) {
53399
53399
  this._mcpServer = _mcpServer;
@@ -53408,7 +53408,7 @@ var ExperimentalMcpServerTasks = class {
53408
53408
  }
53409
53409
  };
53410
53410
 
53411
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js
53411
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.2_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js
53412
53412
  function toolInputElementCount(value, max) {
53413
53413
  let count = 0;
53414
53414
  const stack = [value];
@@ -53457,6 +53457,15 @@ var McpServer = class {
53457
53457
  this._promptHandlersInitialized = false;
53458
53458
  this.server = new Server(serverInfo, options);
53459
53459
  this._maxToolInputElements = resolveMaxToolInputElements(options?.maxToolInputElements);
53460
+ if (options?.capabilities?.tools) {
53461
+ this.setToolRequestHandlers();
53462
+ }
53463
+ if (options?.capabilities?.resources) {
53464
+ this.setResourceRequestHandlers();
53465
+ }
53466
+ if (options?.capabilities?.prompts) {
53467
+ this.setPromptRequestHandlers();
53468
+ }
53460
53469
  }
53461
53470
  /**
53462
53471
  * Access experimental features.
@@ -54264,10 +54273,10 @@ var EMPTY_COMPLETION_RESULT = {
54264
54273
  }
54265
54274
  };
54266
54275
 
54267
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
54276
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.2_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
54268
54277
  import process2 from "node:process";
54269
54278
 
54270
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js
54279
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.2_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js
54271
54280
  var STDIO_DEFAULT_MAX_BUFFER_SIZE = 10 * 1024 * 1024;
54272
54281
  var ReadBuffer = class {
54273
54282
  constructor(options) {
@@ -54304,7 +54313,7 @@ function serializeMessage(message) {
54304
54313
  return JSON.stringify(message) + "\n";
54305
54314
  }
54306
54315
 
54307
- // node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.1_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
54316
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.2_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
54308
54317
  var StdioServerTransport = class {
54309
54318
  constructor(_stdin = process2.stdin, _stdout = process2.stdout, options) {
54310
54319
  this._stdin = _stdin;
@@ -57545,25 +57554,35 @@ function parseAttachmentRows(json3) {
57545
57554
  }
57546
57555
  return rows.sort((a, b) => a.pk - b.pk);
57547
57556
  }
57548
- function attachmentOrderFromNoteData(hex5) {
57557
+ function attachmentRunsFromNoteData(hex5) {
57549
57558
  if (!hex5 || !/^[0-9a-f]+$/i.test(hex5)) return null;
57550
57559
  try {
57551
57560
  const data = gunzipSync5(Buffer.from(hex5, "hex"), { maxOutputLength: MAX_BODY_BYTES });
57552
57561
  const wrapper = embeddedMessage(getField(decodeMessage(data), 2));
57553
57562
  const body = wrapper && embeddedMessage(getField(wrapper, 3));
57554
57563
  if (!body) return null;
57555
- const ids = [];
57564
+ const refs = [];
57556
57565
  for (const run of getFields(body, 5)) {
57557
57566
  const fields = embeddedMessage(run);
57558
57567
  const info = fields && embeddedMessage(getField(fields, 12));
57559
57568
  const id2 = info && stringValue(getField(info, 1));
57560
- if (id2 && !ids.some((seen) => seen.toLowerCase() === id2.toLowerCase())) ids.push(id2);
57569
+ if (!id2) continue;
57570
+ const length = varintValue(getField(fields, 1)) ?? 1;
57571
+ for (let i = 0; i < Math.min(Math.max(length, 1), 64); i++) refs.push(id2);
57561
57572
  }
57562
- return ids;
57573
+ return refs;
57563
57574
  } catch {
57564
57575
  return null;
57565
57576
  }
57566
57577
  }
57578
+ function attachmentOrderFromNoteData(hex5) {
57579
+ const refs = attachmentRunsFromNoteData(hex5);
57580
+ if (!refs) return null;
57581
+ const ids = [];
57582
+ for (const id2 of refs)
57583
+ if (!ids.some((seen) => seen.toLowerCase() === id2.toLowerCase())) ids.push(id2);
57584
+ return ids;
57585
+ }
57567
57586
  function readNoteAttachmentRows(noteId3, dbPath2 = join9(NOTES_CONTAINER_DIR, "NoteStore.sqlite")) {
57568
57587
  const { pk } = parseNoteId2(noteId3);
57569
57588
  const columnList = runSqlite3(
@@ -57587,7 +57606,8 @@ function readNoteAttachmentRows(noteId3, dbPath2 = join9(NOTES_CONTAINER_DIR, "N
57587
57606
  } catch {
57588
57607
  throw new AttachmentStoreError("Attachment rows could not be parsed", "query_error");
57589
57608
  }
57590
- return { rows, bodyOrder: attachmentOrderFromNoteData(lines[2]?.trim()) };
57609
+ const bodyRefs = attachmentRunsFromNoteData(lines[2]?.trim());
57610
+ return { rows, bodyOrder: attachmentOrderFromNoteData(lines[2]?.trim()), bodyRefs };
57591
57611
  }
57592
57612
  function safeComponent(value) {
57593
57613
  if (typeof value !== "string" || !value || value.length > 255) return null;
@@ -57750,13 +57770,16 @@ function assetPathsFor(accountDir, row, onStale) {
57750
57770
  add(file2);
57751
57771
  return found;
57752
57772
  }
57753
- function assembleAttachmentAssets(allRows, bodyOrder, containerDir = NOTES_CONTAINER_DIR) {
57773
+ function assembleAttachmentAssets(allRows, bodyOrder, containerDir = NOTES_CONTAINER_DIR, bodyRefs = null) {
57754
57774
  const rows = allRows.filter((r) => !r.parentDeleted);
57755
57775
  const byPk = new Map(rows.map((r) => [r.pk, r]));
57756
57776
  const roots = rows.filter((r) => r.parentPk === null || !byPk.has(r.parentPk));
57757
57777
  const bodyIndex = /* @__PURE__ */ new Map();
57758
57778
  (bodyOrder ?? []).forEach((id2, index) => bodyIndex.set(id2.toLowerCase(), index));
57759
57779
  const indexOf = (r) => bodyIndex.get(r.identifier.toLowerCase()) ?? null;
57780
+ const refCount = /* @__PURE__ */ new Map();
57781
+ for (const id2 of bodyRefs ?? [])
57782
+ refCount.set(id2.toLowerCase(), (refCount.get(id2.toLowerCase()) ?? 0) + 1);
57760
57783
  const inBody = roots.filter((r) => indexOf(r) !== null);
57761
57784
  const orderSource = inBody.length > 0 ? "body" : "creation";
57762
57785
  const orderedRoots = [
@@ -57790,6 +57813,7 @@ function assembleAttachmentAssets(allRows, bodyOrder, containerDir = NOTES_CONTA
57790
57813
  parentIdentifier: parent?.identifier ?? null,
57791
57814
  filename: row.filename ?? row.mediaFilename,
57792
57815
  bodyIndex: parent ? null : indexOf(row),
57816
+ ...!parent && (refCount.get(row.identifier.toLowerCase()) ?? 0) > 1 ? { bodyReferences: refCount.get(row.identifier.toLowerCase()) } : {},
57793
57817
  assetPaths,
57794
57818
  ...fallbackStale ? { fallbackStale: true } : {},
57795
57819
  previewPath,
@@ -60082,7 +60106,17 @@ var AppleNotesManager = class {
60082
60106
  if (!result.success) throw new Error(result.error || "Could not check folder existence");
60083
60107
  return result.output.trim() === "true";
60084
60108
  }
60085
- /** Insert one file into an unchanged exact note and return Notes' attachment ID. */
60109
+ /**
60110
+ * Insert one file into an unchanged exact note and return Notes' attachment ID.
60111
+ *
60112
+ * Notes.app's `make new attachment` writes the new attachment into the note
60113
+ * body twice (#321): one attachment object, two body references, so the file
60114
+ * shows twice. Every form of the command does it (`at n`, `at end of
60115
+ * attachments of n`, `tell n to make`, an alias), and `every attachment of n`
60116
+ * then lists the same id twice. Deleting the extra entries by index removes
60117
+ * only their body references; the attachment object and its file stay. The
60118
+ * cleanup runs in the same AppleScript so no other edit can interleave.
60119
+ */
60086
60120
  addAttachmentById(id2, expectedBody, filePath) {
60087
60121
  if (!/^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p\d+$/i.test(id2))
60088
60122
  throw new Error("Exact note ID required");
@@ -60094,7 +60128,16 @@ var AppleNotesManager = class {
60094
60128
  if body of n is not ${quote(expectedBody)} and body of n is not (${quote(expectedBody)} & linefeed) then error "Note changed before attachment insertion"
60095
60129
  end considering
60096
60130
  set a to make new attachment at n with data (POSIX file ${quote(filePath)})
60097
- return id of a
60131
+ set newId to id of a
60132
+ set hits to {}
60133
+ set attachmentIds to id of every attachment of n
60134
+ repeat with i from 1 to count of attachmentIds
60135
+ if item i of attachmentIds is newId then set end of hits to i
60136
+ end repeat
60137
+ repeat with j from (count of hits) to 2 by -1
60138
+ delete attachment (item j of hits) of n
60139
+ end repeat
60140
+ return newId
60098
60141
  end tell`);
60099
60142
  if (!result.success)
60100
60143
  throw new Error(
@@ -60102,6 +60145,27 @@ var AppleNotesManager = class {
60102
60145
  );
60103
60146
  return result.output.trim();
60104
60147
  }
60148
+ /**
60149
+ * How many entries Notes' `every attachment` holds for one attachment id
60150
+ * (#321). Read-only. Notes on macOS 27 does not list PDF attachments, so a PDF
60151
+ * counts 0 here; the NoteStore database is the check for those.
60152
+ */
60153
+ countAttachmentEntriesById(noteId3, attachmentId) {
60154
+ if (!/^x-coredata:\/\/[0-9a-f-]+\/ICAttachment\/p\d+$/i.test(attachmentId))
60155
+ throw new Error("Exact attachment ID required");
60156
+ const result = executeAppleScript(`tell application "Notes"
60157
+ set attachmentIds to id of every attachment of note id "${sanitizeId(noteId3)}"
60158
+ set hits to 0
60159
+ repeat with i from 1 to count of attachmentIds
60160
+ if item i of attachmentIds is "${attachmentId}" then set hits to hits + 1
60161
+ end repeat
60162
+ return hits as text
60163
+ end tell`);
60164
+ if (!result.success) throw new Error(result.error || "Could not count attachment entries");
60165
+ const hits = Number(result.output.trim());
60166
+ if (!Number.isInteger(hits) || hits < 0) throw new Error("Unexpected attachment count");
60167
+ return hits;
60168
+ }
60105
60169
  /**
60106
60170
  * Lists all folders in an account with full hierarchical paths.
60107
60171
  *
@@ -61115,8 +61179,8 @@ var AppleNotesManager = class {
61115
61179
  * @throws AttachmentStoreError (`no_fda`, `invalid_id`, `not_found`, `query_error`)
61116
61180
  */
61117
61181
  getAttachmentAssetsById(noteId3) {
61118
- const { rows, bodyOrder } = readNoteAttachmentRows(noteId3);
61119
- return assembleAttachmentAssets(rows, bodyOrder);
61182
+ const { rows, bodyOrder, bodyRefs } = readNoteAttachmentRows(noteId3);
61183
+ return assembleAttachmentAssets(rows, bodyOrder, void 0, bodyRefs);
61120
61184
  }
61121
61185
  /**
61122
61186
  * The note's lead visual: the first image in body order (even when its asset
@@ -71253,7 +71317,7 @@ function registerDirectOperations(server2, manager) {
71253
71317
  };
71254
71318
  tool(
71255
71319
  "add-attachment",
71256
- "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: reads only a regular file in home, temp or /Volumes, refusing hidden paths (~/.ssh, .env) and ~/Library outside iCloud Drive and CloudStorage unless the server sets APPLE_NOTES_MCP_ALLOW_PRIVATE_CONTENT_PATHS=1; 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.",
71320
+ "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: reads only a regular file in home, temp or /Volumes, refusing hidden paths (~/.ssh, .env) and ~/Library outside iCloud Drive and CloudStorage unless the server sets APPLE_NOTES_MCP_ALLOW_PRIVATE_CONTENT_PATHS=1; requires a fresh rich revision, copies at most 64 MiB through a private temporary file, never retries insertion, removes the duplicate copy Notes.app's AppleScript inserts (#321), 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. A PDF that Notes shows twice cannot be cleaned up and is reported as an error: delete the extra copy in Notes.app.",
71257
71321
  { id: noteId, expectedContentHash: revision, ...attachmentInput },
71258
71322
  (args) => attachFile(manager, args)
71259
71323
  );
@@ -71392,6 +71456,7 @@ function attachmentName(path10, filename) {
71392
71456
  return filename;
71393
71457
  }
71394
71458
  var UNCERTAIN = "Attachment insertion outcome uncertain; read the exact note before retrying";
71459
+ var duplicated = (attachmentId, copies) => `Notes.app put new attachment ${attachmentId} into this note ${copies} times and the extra ${copies === 2 ? "copy" : "copies"} could not be removed automatically; ask the user to delete the extra ${copies === 2 ? "copy" : "copies"} in Notes.app, and do not attach the file again`;
71395
71460
  var pause = (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
71396
71461
  var sha256 = (data) => createHash4("sha256").update(data).digest("hex");
71397
71462
  function storedAttachmentIds(manager, id2) {
@@ -71447,6 +71512,7 @@ function storedInsertion(manager, id2, before, bytes, name, returnedId) {
71447
71512
  throw new Error(
71448
71513
  `Notes' database shows new attachment ${attachmentId} on this note, but the note body does not reference it; read the exact note and do not attach the file again`
71449
71514
  );
71515
+ if ((row.bodyReferences ?? 1) > 1) throw new Error(duplicated(attachmentId, row.bodyReferences));
71450
71516
  const expected = sha256(bytes);
71451
71517
  const matches = row.assetPaths.some((path10) => fileMatches(path10, bytes.length, expected));
71452
71518
  if (!matches)
@@ -71497,6 +71563,8 @@ function attachFile(manager, args, checked, progress = { insertionStarted: false
71497
71563
  const saved = manager.saveAttachmentById(id2, attachmentId, verifyPath);
71498
71564
  if (!saved.success || !fileMatches(saved.savedPath ?? verifyPath, bytes.length, sha256(bytes)))
71499
71565
  throw new Error("Attachment bytes were not verified; read the exact note before retrying");
71566
+ const copies = manager.countAttachmentEntriesById(id2, attachmentId);
71567
+ if (copies > 1) throw new Error(duplicated(attachmentId, copies));
71500
71568
  } else {
71501
71569
  const stored = inserted.length === 0 && beforeStored ? storedInsertion(manager, id2, beforeStored, bytes, name, returnedId) : null;
71502
71570
  if (!stored)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-notes-mcp",
3
- "version": "2.15.16",
3
+ "version": "2.15.18",
4
4
  "description": "MCP server for Apple Notes - read, search, create, edit, organize, and export notes on macOS via Claude and other AI assistants",
5
5
  "type": "module",
6
6
  "main": "build/index.js",