@mgcrea/mcp-apple-notes 0.0.0-bootstrap

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Olivier Louvignes <olivier@mgcrea.io>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # @mgcrea/mcp-apple-notes
2
+
3
+ Model Context Protocol server for the macOS **Apple Notes** app.
4
+
5
+ > **Unofficial.** Not affiliated with Apple. It drives the Notes app that is already on your Mac.
6
+
7
+ ## Two lanes
8
+
9
+ - **Apple Events lane** — accounts, folders, bodies, and every mutation. Always the authority:
10
+ after a write, the result is what Notes re-read.
11
+ - **Index lane** — read-only SQLite over `NoteStore.sqlite`, for search and metadata at a scale
12
+ Apple Events cannot reach. Needs Full Disk Access.
13
+
14
+ Unlike Mail, **the Apple Events lane alone is a working server**. Measured on a 921-note library, a
15
+ full-text search takes 97 ms — against the 74 s that forced Mail onto its index. So you can install
16
+ this, use it, and only then decide whether to grant Full Disk Access.
17
+
18
+ It does not scale for ever: there is no index behind it, so every query pays the full cost again.
19
+ The wall is around 5–10k notes. `apple_notes_diagnostics` tells you which lane is answering.
20
+
21
+ | Permission | Needed for | Without it |
22
+ | ---------------------- | --------------------------------- | ----------------------------------------------- |
23
+ | **Automation → Notes** | everything | nothing works; you get a `-1743` error |
24
+ | **Full Disk Access** | search at scale, attachment bytes | the server still runs, on the Apple Events lane |
25
+
26
+ Neither is granted to Notes.app — it is the _reader_ that needs permission.
27
+
28
+ ## Tools
29
+
30
+ Read: `diagnostics`, `list_accounts`, `list_folders`, `list_notes`, `search_notes`, `get_note`,
31
+ `list_attachments`, `save_attachment`.
32
+
33
+ Write, registered **only** when `APPLE_NOTES_ALLOW_WRITES=1` — with the flag off they are invisible
34
+ to the model, not merely refused: `create_note`, `update_note`, `move_note`, `delete_notes`.
35
+
36
+ ## Configuration
37
+
38
+ | Variable | Default | |
39
+ | ---------------------------------- | ------------- | ------------------------------------------------- |
40
+ | `APPLE_NOTES_ALLOW_WRITES` | off | Register the mutating tools. |
41
+ | `APPLE_NOTES_ACCOUNTS` | all | Read-side allowlist, comma-separated. |
42
+ | `APPLE_NOTES_INDEX_MODE` | `auto` | `auto` \| `ro` \| `immutable` \| `off`. |
43
+ | `APPLE_NOTES_STORE` | auto | Explicit `NoteStore.sqlite` path. |
44
+ | `APPLE_NOTES_SEARCH_CACHE_TTL_MS` | `60000` | How long the body corpus stays warm. |
45
+ | `APPLE_NOTES_ATTACHMENT_DIR` | `~/Downloads` | The only directory attachments may be saved into. |
46
+ | `APPLE_NOTES_BODY_MAX_BYTES` | `262144` | Body truncation, to protect the context window. |
47
+ | `APPLE_NOTES_OSASCRIPT_TIMEOUT_MS` | `30000` | Sized for the first-run permission prompt. |
48
+
49
+ ## Notes that will bite you
50
+
51
+ - **Notes takes a note's title from the first line of its body.** Setting `name` is silently
52
+ ignored, so `create_note` prepends the title as a heading instead.
53
+ - **Password-protected notes cannot be read at all.** Their text is AES ciphertext at rest, which
54
+ is why `ZICNOTEDATA` carries crypto columns; no permission makes it readable.
55
+ - **Folders nest.** A folder map has to recurse even if your own library happens to be flat.
56
+ - **Search caches on a TTL, not on invalidation.** Checking whether the cache is stale costs _more_
57
+ over Apple Events (128 ms) than redoing the scan (97 ms), so bounded staleness is the honest
58
+ trade. See [docs/notes.md](../../docs/notes.md).
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {}
package/dist/cli.js ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+ import { D as BUILD_INFO, S as NOTES_SURFACE, a as loadConfig, r as createServer } from "./server-HLS_dM7r.js";
3
+ import { runStdioServer } from "@mgcrea/mcp-apple-core";
4
+ //#region src/cli.ts
5
+ const LOG_PREFIX = "apple-notes-mcp";
6
+ runStdioServer({
7
+ build: BUILD_INFO,
8
+ surface: NOTES_SURFACE,
9
+ logPrefix: LOG_PREFIX,
10
+ start: async (logger) => {
11
+ const config = loadConfig();
12
+ const { server } = createServer({
13
+ config,
14
+ logger
15
+ });
16
+ return {
17
+ server,
18
+ banner: `writes=${config.allowWrites ? "ENABLED" : "disabled"}, accounts=${config.accounts.length ? config.accounts.join("/") : "all"}, index=${config.indexMode}`
19
+ };
20
+ }
21
+ }).catch((err) => {
22
+ console.error(`[${LOG_PREFIX}] fatal:`, err);
23
+ process.exit(1);
24
+ });
25
+ //#endregion
26
+ export {};
27
+
28
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { runStdioServer } from \"@mgcrea/mcp-apple-core\";\n\nimport { BUILD_INFO } from \"./build-info.js\";\nimport { NOTES_SURFACE } from \"./client/errors.js\";\nimport { loadConfig } from \"./config.js\";\nimport { createServer } from \"./server.js\";\n\nconst LOG_PREFIX = \"apple-notes-mcp\";\n\nrunStdioServer({\n build: BUILD_INFO,\n surface: NOTES_SURFACE,\n logPrefix: LOG_PREFIX,\n start: async (logger) => {\n const config = loadConfig();\n const { server } = createServer({ config, logger });\n return {\n server,\n banner:\n `writes=${config.allowWrites ? \"ENABLED\" : \"disabled\"}, ` +\n `accounts=${config.accounts.length ? config.accounts.join(\"/\") : \"all\"}, ` +\n `index=${config.indexMode}`,\n };\n },\n}).catch((err: unknown) => {\n console.error(`[${LOG_PREFIX}] fatal:`, err);\n process.exit(1);\n});\n"],"mappings":";;;;AAQA,MAAM,aAAa;AAEnB,eAAe;CACb,OAAO;CACP,SAAS;CACT,WAAW;CACX,OAAO,OAAO,WAAW;EACvB,MAAM,SAAS,WAAW;EAC1B,MAAM,EAAE,WAAW,aAAa;GAAE;GAAQ;EAAO,CAAC;EAClD,OAAO;GACL;GACA,QACE,UAAU,OAAO,cAAc,YAAY,WAAW,aAC1C,OAAO,SAAS,SAAS,OAAO,SAAS,KAAK,GAAG,IAAI,MAAM,UAC9D,OAAO;EACpB;CACF;AACF,CAAC,CAAC,CAAC,OAAO,QAAiB;CACzB,QAAQ,MAAM,IAAI,WAAW,WAAW,GAAG;CAC3C,QAAQ,KAAK,CAAC;AAChB,CAAC"}
@@ -0,0 +1,433 @@
1
+ import { AppBusyError as NotesBusyError, AppNotRunningError as NotesNotRunningError, AppleAutomationError, AppleAutomationError as AppleNotesError, BuildInfo, Logger, OsascriptRunner, ReadOnlyMode, StoreFacts, SurfaceContext } from "@mgcrea/mcp-apple-core";
2
+ import { z } from "zod";
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { DatabaseSync } from "node:sqlite";
5
+ //#region src/build-info.d.ts
6
+ declare const BUILD_INFO: BuildInfo;
7
+ //#endregion
8
+ //#region src/client/errors.d.ts
9
+ /** Named in every user-facing error and in the env vars they mention. */
10
+ declare const NOTES_SURFACE: SurfaceContext;
11
+ /** A NoteRef no longer resolves — the note was deleted, or the index is stale. */
12
+ declare class NoteNotFoundError extends AppleAutomationError {
13
+ readonly name = "NoteNotFoundError";
14
+ constructor(ref: string);
15
+ }
16
+ /**
17
+ * The note is password-protected and Notes will not hand over its text.
18
+ *
19
+ * The file lane cannot help either: locked notes are AES ciphertext at rest,
20
+ * which is why `ZICNOTEDATA` carries `ZCRYPTOINITIALIZATIONVECTOR` and
21
+ * `ZCRYPTOTAG` columns. Saying so is more useful than an empty body.
22
+ */
23
+ declare class NoteLockedError extends AppleAutomationError {
24
+ readonly name = "NoteLockedError";
25
+ constructor(ref: string);
26
+ }
27
+ //#endregion
28
+ //#region src/client/locate.d.ts
29
+ type LocateResult = StoreFacts & {
30
+ storePath: string;
31
+ /** Where attachment bytes live, one directory per account. */
32
+ mediaRoot: string;
33
+ reason: string | null;
34
+ };
35
+ declare const defaultStorePath: (home?: string) => string;
36
+ declare const locateStore: (opts?: {
37
+ storePath?: string | undefined;
38
+ home?: string;
39
+ }) => LocateResult;
40
+ //#endregion
41
+ //#region src/config.d.ts
42
+ /**
43
+ * Configuration is environment-only — this server holds no secret at all, its
44
+ * access is the macOS permission the user granted.
45
+ *
46
+ * `allowWrites`, `debug`, `osascriptPath`, `osascriptTimeoutMs` and `maxResults`
47
+ * come from `BaseConfigSchema`.
48
+ */
49
+ declare const ConfigSchema: z.ZodObject<{
50
+ allowWrites: z.ZodDefault<z.ZodBoolean>;
51
+ debug: z.ZodDefault<z.ZodBoolean>;
52
+ osascriptPath: z.ZodDefault<z.ZodString>;
53
+ osascriptTimeoutMs: z.ZodDefault<z.ZodNumber>;
54
+ maxResults: z.ZodDefault<z.ZodNumber>;
55
+ accounts: z.ZodDefault<z.ZodArray<z.ZodString>>;
56
+ storePath: z.ZodOptional<z.ZodString>;
57
+ indexMode: z.ZodDefault<z.ZodEnum<{
58
+ auto: "auto";
59
+ immutable: "immutable";
60
+ off: "off";
61
+ ro: "ro";
62
+ }>>;
63
+ degradedMaxNotes: z.ZodDefault<z.ZodNumber>;
64
+ bodyMaxBytes: z.ZodDefault<z.ZodNumber>;
65
+ attachmentDir: z.ZodDefault<z.ZodString>;
66
+ searchCacheTtlMs: z.ZodDefault<z.ZodNumber>;
67
+ }, z.core.$strict>;
68
+ type Config = z.infer<typeof ConfigSchema>;
69
+ /**
70
+ * `env` is a parameter with a default so tests are hermetic — they pass their
71
+ * own object rather than mutating (and having to restore) process.env.
72
+ */
73
+ declare const loadConfig: (env?: NodeJS.ProcessEnv) => Config;
74
+ //#endregion
75
+ //#region src/client/store.d.ts
76
+ type NoteRow = {
77
+ primaryKey: number;
78
+ title: string | null;
79
+ snippet: string | null;
80
+ modified: string | null;
81
+ created: string | null;
82
+ folderPk: number | null;
83
+ locked: boolean;
84
+ };
85
+ type StoreCapabilities = {
86
+ fingerprint: string;
87
+ /**
88
+ * The persistent store UUID, which is the middle segment of every note's
89
+ * Apple Events id (`x-coredata://<uuid>/ICNote/p<N>`). Reading it here lets an
90
+ * index row be turned into a ref without asking Notes anything.
91
+ */
92
+ storeUuid: string | null;
93
+ /** `Z_ENT` for `ICNote`, looked up by name rather than hardcoded to 12. */
94
+ noteEnt: number | null;
95
+ /** Present on this schema, so the queries can use them. */
96
+ has: {
97
+ title: boolean;
98
+ snippet: boolean;
99
+ widgetSnippet: boolean;
100
+ folder: boolean;
101
+ locked: boolean;
102
+ };
103
+ deletionColumns: string[];
104
+ missing: string[];
105
+ };
106
+ declare const introspect: (db: DatabaseSync) => StoreCapabilities;
107
+ type SearchFilters = {
108
+ query?: string | undefined;
109
+ folderPk?: number | undefined;
110
+ modifiedAfter?: string | undefined;
111
+ modifiedBefore?: string | undefined;
112
+ limit: number;
113
+ offset: number;
114
+ };
115
+ declare class NoteStore {
116
+ #private;
117
+ readonly caps: StoreCapabilities;
118
+ readonly mode: "ro" | "immutable";
119
+ constructor(db: DatabaseSync, caps: StoreCapabilities, mode: "ro" | "immutable");
120
+ count(): number;
121
+ /**
122
+ * Title and snippet search.
123
+ *
124
+ * Deliberately not full text: no indexed column carries the body. Callers that
125
+ * need body matching combine this with `bodyOf` or fall back to the Apple
126
+ * Events bulk scan.
127
+ */
128
+ search(filters: SearchFilters): NoteRow[];
129
+ byPrimaryKey(pk: number): NoteRow | null;
130
+ /**
131
+ * The note body, decoded from the gzipped protobuf.
132
+ *
133
+ * Returns null for password-protected notes: their `ZDATA` is AES ciphertext,
134
+ * flagged by a non-null `ZCRYPTOTAG`. No permission makes that readable.
135
+ */
136
+ bodyOf(pk: number): {
137
+ text: string | null;
138
+ via: string | null;
139
+ encrypted: boolean;
140
+ };
141
+ close(): void;
142
+ }
143
+ declare const openStore: (path: string, mode: ReadOnlyMode, logger?: Logger) => NoteStore;
144
+ //#endregion
145
+ //#region src/client/notes.d.ts
146
+ type LaneStatus = {
147
+ applescript: "live" | "unavailable";
148
+ index: "live" | "unavailable" | "disabled";
149
+ indexMode: string | null;
150
+ storeFingerprint: string | null;
151
+ reason: string | null;
152
+ };
153
+ type NoteAccount = {
154
+ id: string | null;
155
+ name: string | null;
156
+ defaultFolder: string | null;
157
+ folderCount: number;
158
+ noteCount: number;
159
+ };
160
+ type NoteFolder = {
161
+ id: string | null;
162
+ name: string | null;
163
+ accountId: string | null;
164
+ accountName: string | null;
165
+ depth: number;
166
+ shared: boolean;
167
+ noteCount: number | null;
168
+ };
169
+ type NoteSummary = {
170
+ ref: string;
171
+ title: string | null;
172
+ snippet: string | null;
173
+ folder: string | null;
174
+ account: string | null;
175
+ modified: string | null;
176
+ created: string | null;
177
+ locked: boolean;
178
+ source: "index" | "apple-events";
179
+ };
180
+ type AppleNotesClientOptions = {
181
+ config: Config;
182
+ logger?: Logger | undefined;
183
+ /** Injected by tests so nothing spawns a process or touches a real Notes. */
184
+ osascript?: OsascriptRunner | undefined;
185
+ };
186
+ /**
187
+ * The Notes client.
188
+ *
189
+ * Two lanes, and which one answers depends on what was granted:
190
+ *
191
+ * - **Apple Events** — accounts, folders, every mutation, and bodies. Always the
192
+ * authority: after a write, the result is what Notes re-read.
193
+ * - **Index** — `NoteStore.sqlite`, for search and metadata at a scale Apple
194
+ * Events cannot reach.
195
+ *
196
+ * The discipline the measurements impose is in `jxa/core.ts`: bulk array fetches
197
+ * only, never `whose`, never a property read per note.
198
+ */
199
+ declare class AppleNotesClient {
200
+ #private;
201
+ readonly config: Config;
202
+ readonly runner: OsascriptRunner;
203
+ constructor(opts: AppleNotesClientOptions);
204
+ locate(): LocateResult;
205
+ /** Open the index once and remember the outcome, including the failure. */
206
+ index(): NoteStore | null;
207
+ /**
208
+ * Probe both lanes.
209
+ *
210
+ * Apple Events is probed twice with a gap: the first event is what triggers
211
+ * the Automation prompt, and it can return failure while the user is still
212
+ * deciding — reporting "denied" then would be wrong.
213
+ */
214
+ lanes(): Promise<LaneStatus>;
215
+ accounts(): Promise<NoteAccount[]>;
216
+ folders(): Promise<NoteFolder[]>;
217
+ /**
218
+ * Note metadata.
219
+ *
220
+ * Prefers the index — it holds the same fields, costs no Apple Event, and
221
+ * keeps working as a library grows past the point where a bulk fetch stops
222
+ * being instant.
223
+ */
224
+ listNotes(opts: {
225
+ folder?: string | undefined;
226
+ limit: number;
227
+ }): Promise<NoteSummary[]>;
228
+ /**
229
+ * Search.
230
+ *
231
+ * `title` uses the index's title and snippet columns, which is a SQL query.
232
+ * `full` needs the body, which no indexed column carries — so it decodes the
233
+ * gzipped protobuf when the index is available and falls back to the Apple
234
+ * Events bulk scan when it is not. Either way the match happens in JS over a
235
+ * cached corpus, because `whose` is 6.9x slower than fetching and filtering.
236
+ */
237
+ searchNotes(opts: {
238
+ query: string;
239
+ scope: "title" | "full";
240
+ limit: number;
241
+ offset: number;
242
+ }): Promise<{
243
+ notes: NoteSummary[];
244
+ source: "index" | "apple-events";
245
+ scope: string;
246
+ }>;
247
+ getNote(ref: string, opts?: {
248
+ body?: boolean;
249
+ }): Promise<{
250
+ summary: NoteSummary;
251
+ body: string | null;
252
+ bodySource: string | null;
253
+ }>;
254
+ attachments(ref: string): Promise<{
255
+ id: string | null;
256
+ name: string | null;
257
+ url: string | null;
258
+ contentIdentifier: string | null;
259
+ }[]>;
260
+ /**
261
+ * Save an attachment's bytes.
262
+ *
263
+ * The scripting dictionary exposes `name`, `id`, `URL` and `content
264
+ * identifier` but **no filesystem path**, so the bytes can only come from the
265
+ * media directory — which is Full Disk Access territory. Without it this
266
+ * reports what is missing rather than failing obscurely.
267
+ */
268
+ saveAttachment(ref: string, attachmentId: string, targetDir?: string, opts?: {
269
+ overwrite?: boolean;
270
+ }): Promise<{
271
+ path: string;
272
+ bytes: number;
273
+ }>;
274
+ createNote(opts: {
275
+ title?: string;
276
+ body?: string;
277
+ folderId?: string;
278
+ }): Promise<unknown>;
279
+ updateNote(ref: string, body: string, mode: "replace" | "append"): Promise<unknown>;
280
+ moveNote(ref: string, folderId: string): Promise<unknown>;
281
+ deleteNotes(refs: string[]): Promise<unknown>;
282
+ }
283
+ //#endregion
284
+ //#region src/client/protobuf.d.ts
285
+ /**
286
+ * Minimal protobuf wire-format reader, enough to pull the body text out of a
287
+ * `ZICNOTEDATA.ZDATA` blob.
288
+ *
289
+ * ## Why it does not hardcode Apple's schema
290
+ *
291
+ * The usual approach compiles Apple's unpublished `.proto` and reads
292
+ * `document.note.note_text` by field number. That breaks the first time Apple
293
+ * renumbers a field, and this project already budgets for the schema drifting
294
+ * on every macOS release.
295
+ *
296
+ * So this walks the wire format structurally, and the path is *measured* rather
297
+ * than assumed — see `NOTE_TEXT_PATH`.
298
+ *
299
+ * Wire format, for reference:
300
+ * tag = varint; field = tag >> 3; wire type = tag & 7
301
+ * 0 varint · 1 fixed64 · 2 length-delimited · 5 fixed32 · 3/4 groups (legacy)
302
+ *
303
+ * Pure and I/O-free: callers gunzip, callers decide what to do with the result.
304
+ */
305
+ type Varint = {
306
+ value: number;
307
+ next: number;
308
+ };
309
+ /** Read a base-128 varint. Returns null rather than throwing on a truncated one. */
310
+ declare const readVarint: (buf: Uint8Array, pos: number) => Varint | null;
311
+ type WireField = {
312
+ field: number;
313
+ wire: number;
314
+ value?: number;
315
+ from?: number;
316
+ to?: number;
317
+ };
318
+ /**
319
+ * Split a buffer into wire-format fields. Returns null if the bytes are not a
320
+ * well-formed message ending exactly on the boundary — that "exactly" is what
321
+ * makes this usable as a test for whether a blob is a nested message.
322
+ */
323
+ declare const parseFields: (buf: Uint8Array, start?: number, end?: number) => WireField[] | null;
324
+ type StringCandidate = {
325
+ path: string;
326
+ text: string;
327
+ length: number;
328
+ };
329
+ /**
330
+ * Every string in the blob, with the field path that reached it.
331
+ *
332
+ * A length-delimited field can be both a valid message and valid text, so both
333
+ * readings are followed. That ambiguity is the normal case, not an error.
334
+ */
335
+ declare const collectStrings: (buf: Uint8Array, maxDepth?: number) => StringCandidate[];
336
+ /**
337
+ * Where the body lives: `document(2) -> note(3) -> note_text(2)`.
338
+ *
339
+ * Measured, not assumed. Running the structural walk over 53 real notes and
340
+ * checking each result against the same note's Apple Events plaintext, every
341
+ * note read from this path matched exactly (53/53), and picking the longest
342
+ * string instead matched only 27/53 — on the other 26 an attribute-run field
343
+ * outran the body by ~60 characters. That failure is silent: it returns
344
+ * plausible, wrong text rather than an error.
345
+ */
346
+ declare const NOTE_TEXT_PATH = "2.3.2";
347
+ type DecodedNote = {
348
+ text: string | null;
349
+ path: string | null;
350
+ /** `pinned` or `longest`. A shift to `longest` is the schema-drift signal. */
351
+ via: "pinned" | "longest" | null;
352
+ candidateCount: number;
353
+ };
354
+ /**
355
+ * The note body.
356
+ *
357
+ * Reads the pinned path first and falls back to the longest string only if that
358
+ * path is absent, reporting which route it took so drift is detectable rather
359
+ * than silently wrong.
360
+ */
361
+ declare const extractNoteText: (buf: Uint8Array, preferPath?: string | null) => DecodedNote;
362
+ //#endregion
363
+ //#region src/client/ref.d.ts
364
+ /**
365
+ * NoteRef — the one identifier any tool accepts or returns.
366
+ *
367
+ * Notes' own id is already globally unique and resolvable without a container,
368
+ * unlike Mail's message ids: `x-coredata://<store>/ICNote/p<N>` addresses a note
369
+ * outright, so this carries no folder and needs no lookup ladder.
370
+ *
371
+ * Wire format: n1:<coredata-id>
372
+ *
373
+ * The `n1:` prefix is deliberate. If the id scheme ever changes, a versioned
374
+ * prefix makes that an additive change instead of a silent reinterpretation of
375
+ * every ref already in a conversation.
376
+ *
377
+ * Deliberately NOT shared with Mail's `ref.ts`: that codec's `#(\d+)$` and
378
+ * `Number.isSafeInteger` both assume an integer id, which this is not.
379
+ */
380
+ declare const REF_VERSION = "n1";
381
+ type NoteRef = {
382
+ /** The full Core Data URI, which is what Apple Events resolves. */
383
+ id: string;
384
+ /** `Z_PK` in `ZICCLOUDSYNCINGOBJECT` — measured, see docs/notes.md. */
385
+ primaryKey: number;
386
+ };
387
+ declare const encodeRef: (id: string) => string;
388
+ declare const decodeRef: (raw: string) => NoteRef;
389
+ /** Build a ref from an index row, which only ever knows the primary key. */
390
+ declare const refFromPrimaryKey: (storeUuid: string, primaryKey: number) => string;
391
+ //#endregion
392
+ //#region src/server.d.ts
393
+ declare const SERVER_NAME: string;
394
+ declare const SERVER_VERSION: string;
395
+ type CreateServerOptions = {
396
+ config: Config;
397
+ logger?: Logger;
398
+ /** Injected by tests so nothing spawns a process or touches a real Notes. */
399
+ osascript?: OsascriptRunner;
400
+ };
401
+ type CreatedServer = {
402
+ server: McpServer;
403
+ client: AppleNotesClient;
404
+ };
405
+ /**
406
+ * Build the server. Side-effect free: it opens no connection, spawns no
407
+ * process and reads no file, so a test can construct it freely and every
408
+ * external dependency arrives through an option.
409
+ */
410
+ declare const createServer: (opts: CreateServerOptions) => CreatedServer;
411
+ //#endregion
412
+ //#region src/tools/index.d.ts
413
+ type ToolContext = {
414
+ /**
415
+ * Register the mutating tools too. Off by default — with the flag off they are
416
+ * not merely refused, they are invisible and cannot be called at all.
417
+ */
418
+ allowWrites: boolean;
419
+ };
420
+ /**
421
+ * Register the Apple Notes tools.
422
+ *
423
+ * The registered set is a pure function of `allowWrites` and nothing else. In
424
+ * particular it does NOT vary with whether Full Disk Access is granted: that is
425
+ * a runtime condition which can change while the process lives, and MCP clients
426
+ * cache the tool list, so a tool that appears and disappears would leave clients
427
+ * calling names the server no longer has. Tools that need the index instead
428
+ * report their source, or explain what is missing.
429
+ */
430
+ declare const registerTools: (server: McpServer, client: AppleNotesClient, ctx: ToolContext) => void;
431
+ //#endregion
432
+ export { AppleNotesClient, AppleNotesError, BUILD_INFO, type BuildInfo, type Config, type CreateServerOptions, type DecodedNote, type LaneStatus, type LocateResult, NOTES_SURFACE, NOTE_TEXT_PATH, type NoteAccount, type NoteFolder, NoteLockedError, NoteNotFoundError, type NoteRef, type NoteRow, NoteStore, type NoteSummary, NotesBusyError, NotesNotRunningError, REF_VERSION, SERVER_NAME, SERVER_VERSION, type StoreCapabilities, type ToolContext, collectStrings, createServer, decodeRef, defaultStorePath, encodeRef, extractNoteText, introspect, loadConfig, locateStore, openStore, parseFields, readVarint, refFromPrimaryKey, registerTools };
433
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/build-info.ts","../src/client/errors.ts","../src/client/locate.ts","../src/config.ts","../src/client/store.ts","../src/client/notes.ts","../src/client/protobuf.ts","../src/client/ref.ts","../src/server.ts","../src/tools/index.ts"],"mappings":";;;;;cAkBa,YAAY;;;;cCTZ,eAAe;;cAiBf,0BAA0B;WACnB;EAElB,YAAY;;;;;;;;;cAeD,wBAAwB;WACjB;EAElB,YAAY;;;;KC1BF,eAAe;EACzB;;EAEA;EACA;;cAGW,mBAAgB;cAGhB,cAAW;EACd;EAAgC;MACvC;;;;;;;;;;cCbG,cAAY,EAAA;;;;;;;;;;;;;;;;;;GAiCP,EAAA,KAAA;KAEC,SAAS,EAAE,aAAa;;;;;cAMvB,aAAU,MAAS,OAAO,eAA2B;;;KC1BtD;EACV;EACA;EACA;EACA;EACA;EACA;EACA;;KAGU;EACV;;;;;;EAMA;;EAEA;;EAEA;IACE;IACA;IACA;IACA;IACA;;EAEF;EACA;;cAUW,aAAU,IAAQ,iBAAe;KA4DlC;EACV;EACA;EACA;EACA;EACA;EACA;;cAGW;;WAEF,MAAM;WACN;EAET,YAAY,IAAI,cAAc,MAAM,mBAAmB;EA+CvD;;;;;;;;EAcA,OAAO,SAAS,gBAAgB;EAwChC,aAAa,aAAa;;;;;;;EAa1B,OAAO;IAAe;IAAqB;IAAoB;;EAmB/D;;cAKW,YAAS,cAAgB,MAAQ,cAAY,SAAW,WAAS;;;KCrQlE;EACV;EACA;EACA;EACA;EACA;;KAGU;EACV;EACA;EACA;EACA;EACA;;KAGU;EACV;EACA;EACA;EACA;EACA;EACA;EACA;;KAGU;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;KAUU;EACV,QAAQ;EACR,SAAS;;EAET,YAAY;;;;;;;;;;;;;;;cAgBD;;WACF,QAAQ;WACR,QAAQ;EAMjB,YAAY,MAAM;EAalB,UAAU;;EAKV,SAAS;;;;;;;;EAoBH,SAAS,QAAQ;EA8BjB,YAAY,QAAQ;EAKpB,WAAW,QAAQ;;;;;;;;EAYnB,UAAU;IAAQ;IAA6B;MAAkB,QAAQ;;;;;;;;;;EA+GzE,YAAY;IAChB;IACA;IACA;IACA;MACE;IAAU,OAAO;IAAe;IAAkC;;EA4BhE,QACJ,aACA;IAAQ;MACP;IAAU,SAAS;IAAa;IAAqB;;EAwDlD,YAAY,cAAc;IAE5B;IACA;IACA;IACA;;;;;;;;;;EAeE,eACJ,aACA,sBACA,oBACA;IAAQ;MACP;IACD;IACA;;EAsFI,WAAW;IAAQ;IAAgB;IAAe;MAAsB;EAKxE,WAAW,aAAa,cAAc,6BAA6B;EAMnE,SAAS,aAAa,mBAAmB;EAMzC,YAAY,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;KClezB;EAAW;EAAe;;;cAGzB,aAAU,KAAS,YAAU,gBAAgB;KAiB9C;EACV;EACA;EACA;EACA;EACA;;;;;;;cAQW,cAAW,KACjB,YAAU,gBAAA,iBAGd;KAqDS;EAAoB;EAAc;EAAc;;;;;;;;cAQ/C,iBAAc,KAAS,YAAU,sBAAyB;;;;;;;;;;;cA4B1D;KAED;EACV;EACA;;EAEA;EACA;;;;;;;;;cAUW,kBAAe,KACrB,YAAU,+BAEd;;;;;;;;;;;;;;;;;;;cCjKU;KAKD;;EAEV;;EAEA;;cAGW,YAAS;cAET,YAAS,gBAAkB;;cAuB3B,oBAAiB,mBAAqB;;;cChDtC;cACA;KAED;EACV,QAAQ;EACR,SAAS;;EAET,YAAY;;KAGF;EACV,QAAQ;EACR,QAAQ;;;;;;;cAQG,eAAY,MAAU,wBAAsB;;;KCpB7C;;;;;EAKV;;;;;;;;;;;;cAaW,gBAAa,QAChB,WAAS,QACT,kBAAgB,KACnB"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import { C as NoteLockedError, D as BUILD_INFO, E as NotesNotRunningError, S as NOTES_SURFACE, T as NotesBusyError, _ as encodeRef, a as loadConfig, b as locateStore, c as introspect, d as collectStrings, f as extractNoteText, g as decodeRef, h as REF_VERSION, i as registerTools, l as openStore, m as readVarint, n as SERVER_VERSION, o as AppleNotesClient, p as parseFields, r as createServer, s as NoteStore, t as SERVER_NAME, u as NOTE_TEXT_PATH, v as refFromPrimaryKey, w as NoteNotFoundError, x as AppleNotesError, y as defaultStorePath } from "./server-HLS_dM7r.js";
2
+ export { AppleNotesClient, AppleNotesError, BUILD_INFO, NOTES_SURFACE, NOTE_TEXT_PATH, NoteLockedError, NoteNotFoundError, NoteStore, NotesBusyError, NotesNotRunningError, REF_VERSION, SERVER_NAME, SERVER_VERSION, collectStrings, createServer, decodeRef, defaultStorePath, encodeRef, extractNoteText, introspect, loadConfig, locateStore, openStore, parseFields, readVarint, refFromPrimaryKey, registerTools };