@mgcrea/mcp-apple-calendar 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.
@@ -0,0 +1,625 @@
1
+ import { AppBusyError as CalendarBusyError, AppNotRunningError as CalendarNotRunningError, AppleAutomationError, AppleAutomationError as AppleCalendarError, 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/store.d.ts
9
+ type StoreCapabilities = {
10
+ fingerprint: string;
11
+ /** Present columns, so an Apple rename degrades one field instead of the lane. */
12
+ itemColumns: Set<string>;
13
+ calendarColumns: Set<string>;
14
+ occurrenceColumns: Set<string>;
15
+ recurrenceColumns: Set<string>;
16
+ storeColumns: Set<string>;
17
+ /**
18
+ * Whether recurrences can be expanded at all.
19
+ *
20
+ * False is a capability downgrade, never a throw: leg 1 of a range query
21
+ * (items carrying no recurrence rule) stays correct at any horizon, so the
22
+ * honest degradation is to answer with it and say that repeating events were
23
+ * not expanded — not to fail, and above all not to quietly return fewer rows.
24
+ */
25
+ hasOccurrenceCache: boolean;
26
+ hasOccurrenceDays: boolean;
27
+ hasRecurrence: boolean;
28
+ hasExceptionDates: boolean;
29
+ hasLocation: boolean;
30
+ hasAttachments: boolean;
31
+ hasParticipants: boolean;
32
+ hasAlarms: boolean;
33
+ epochOffset: number;
34
+ };
35
+ /** One row of the union, still in store units. Rendering happens above this. */
36
+ type EventRow = {
37
+ itemPk: number;
38
+ uuid: string | null;
39
+ calendarPk: number | null;
40
+ calendarUuid: string | null;
41
+ calendarTitle: string | null;
42
+ summary: string | null;
43
+ description: string | null;
44
+ url: string | null;
45
+ conferenceUrl: string | null;
46
+ locationTitle: string | null;
47
+ startApple: number | null;
48
+ endApple: number | null;
49
+ allDay: boolean;
50
+ startTz: string | null;
51
+ endTz: string | null;
52
+ status: number | null;
53
+ invitationStatus: number | null;
54
+ availability: number | null;
55
+ hasRecurrences: boolean;
56
+ hasAttendees: boolean;
57
+ /** Set on a detached occurrence: which series it broke away from, and when. */
58
+ origItemPk: number | null;
59
+ origDateApple: number | null;
60
+ /** Which leg produced this row. Reported so a caller can see the expansion working. */
61
+ source: "item" | "occurrence";
62
+ };
63
+ type RangeQuery = {
64
+ /** Apple-seconds, inclusive lower bound. */
65
+ fromApple: number;
66
+ /** Apple-seconds, exclusive upper bound. */
67
+ toApple: number;
68
+ /** `Calendar.UUID` values. Empty means every calendar. */
69
+ calendarUuids?: readonly string[];
70
+ limit: number;
71
+ };
72
+ type SearchQuery = RangeQuery & {
73
+ text: string;
74
+ scope: "summary" | "full";
75
+ };
76
+ /** How far the cached expansion actually reaches, in Apple-seconds. */
77
+ type Coverage = {
78
+ fromApple: number;
79
+ toApple: number;
80
+ rows: number;
81
+ } | null;
82
+ type IndexCalendar = {
83
+ uuid: string | null;
84
+ title: string | null;
85
+ color: string | null;
86
+ type: string | null;
87
+ accountName: string | null;
88
+ isSubscribed: boolean;
89
+ isPublished: boolean;
90
+ /**
91
+ * Whether this calendar is shared with other people.
92
+ *
93
+ * Derived from `sharing_status`, and INFERRED rather than measured: the only
94
+ * values observed are 0, 1 and NULL, with 1 on the two calendars known to be
95
+ * shared. The raw value stays on the result so a caller can disagree.
96
+ *
97
+ * It matters because writing to a shared calendar is visible to whoever else
98
+ * is on it, and nothing else in the output says so.
99
+ */
100
+ isShared: boolean;
101
+ sharingStatus: number | null;
102
+ };
103
+ type IndexAccount = {
104
+ name: string | null;
105
+ type: number | null;
106
+ calendars: number;
107
+ };
108
+ declare class CalendarStore {
109
+ #private;
110
+ readonly db: DatabaseSync;
111
+ readonly mode: string;
112
+ readonly caps: StoreCapabilities;
113
+ constructor(db: DatabaseSync, mode: string, caps: StoreCapabilities);
114
+ /**
115
+ * LEG 1 — items carried by the table itself.
116
+ *
117
+ * Overlap, not containment: `start < to AND COALESCE(end, start) > from`. A
118
+ * naive `BETWEEN` on the start silently drops the all-hands that began at
119
+ * 09:00 when the caller asked about 10:00 onward.
120
+ *
121
+ * Items whose occurrences are expanded in the cache are excluded here and
122
+ * picked up by leg 2, so a repeating event is not also returned once at its
123
+ * master start.
124
+ */
125
+ rangeItems(q: RangeQuery): EventRow[];
126
+ /**
127
+ * LEG 2 — expanded occurrences.
128
+ *
129
+ * `OccurrenceCache.event_id` -> `CalendarItem.ROWID` was measured at a 100%
130
+ * resolve rate. `occurrence_date` is the start; `occurrence_start_date` exists
131
+ * too and reaches only +256 days against `occurrence_date`'s +724, so using it
132
+ * would silently truncate the far half of the window.
133
+ */
134
+ rangeOccurrences(q: RangeQuery): EventRow[];
135
+ /**
136
+ * Text search, unbounded in time by default.
137
+ *
138
+ * Searching is the one place a caller legitimately wants all of history, so
139
+ * the window is the caller's to set rather than a default. Runs over items
140
+ * only: an occurrence carries no text of its own, and matching the series once
141
+ * is what a search result should be.
142
+ */
143
+ searchItems(q: SearchQuery): EventRow[];
144
+ /** One event by its Apple Events uid. The bridge measured 198/198 exact. */
145
+ byUuid(uuid: string): EventRow | null;
146
+ /**
147
+ * How far the expansion reaches.
148
+ *
149
+ * Published with every range result. Measured at -732 to +724 days on the
150
+ * probed store, which is a real expansion rather than a month-view cache —
151
+ * but it is still an edge, and nothing guarantees the next machine's is as
152
+ * deep. A range running past it must say so rather than return a short list.
153
+ */
154
+ coverage(): Coverage;
155
+ calendars(): IndexCalendar[];
156
+ accounts(): IndexAccount[];
157
+ close(): void;
158
+ }
159
+ declare const introspect: (db: DatabaseSync) => StoreCapabilities;
160
+ declare const openStore: (path: string | null, mode: ReadOnlyMode, logger?: Logger) => CalendarStore | null;
161
+ //#endregion
162
+ //#region src/config.d.ts
163
+ /**
164
+ * Configuration is environment-only — this server holds no secret at all, its
165
+ * access is the macOS permission the user granted.
166
+ *
167
+ * `allowWrites`, `debug`, `osascriptPath`, `osascriptTimeoutMs` and `maxResults`
168
+ * come from `BaseConfigSchema`.
169
+ *
170
+ * Note what is deliberately ABSENT relative to `packages/reminders`: there is no
171
+ * `searchCacheTtlMs` and no degraded-listing cap, because both exist there to
172
+ * manage an Apple Events READ lane. Calendar has none by design
173
+ * (`docs/distribution.md`), and config for a lane that does not exist would
174
+ * advertise a fallback this server cannot provide.
175
+ */
176
+ declare const ConfigSchema: z.ZodObject<{
177
+ allowWrites: z.ZodDefault<z.ZodBoolean>;
178
+ debug: z.ZodDefault<z.ZodBoolean>;
179
+ osascriptPath: z.ZodDefault<z.ZodString>;
180
+ osascriptTimeoutMs: z.ZodDefault<z.ZodNumber>;
181
+ maxResults: z.ZodDefault<z.ZodNumber>;
182
+ accounts: z.ZodDefault<z.ZodArray<z.ZodString>>;
183
+ calendars: z.ZodDefault<z.ZodArray<z.ZodString>>;
184
+ storePath: z.ZodOptional<z.ZodString>;
185
+ indexMode: z.ZodDefault<z.ZodEnum<{
186
+ auto: "auto";
187
+ immutable: "immutable";
188
+ off: "off";
189
+ ro: "ro";
190
+ }>>;
191
+ defaultCalendar: z.ZodOptional<z.ZodString>;
192
+ defaultRangeDays: z.ZodDefault<z.ZodNumber>;
193
+ maxRangeDays: z.ZodDefault<z.ZodNumber>;
194
+ defaultEventDurationMinutes: z.ZodDefault<z.ZodNumber>;
195
+ includeDeclined: z.ZodDefault<z.ZodBoolean>;
196
+ includeCancelled: z.ZodDefault<z.ZodBoolean>;
197
+ timeZone: z.ZodOptional<z.ZodString>;
198
+ }, z.core.$strict>;
199
+ type Config = z.infer<typeof ConfigSchema>;
200
+ /**
201
+ * `env` is a parameter with a default so tests are hermetic — they pass their
202
+ * own object rather than mutating (and having to restore) process.env.
203
+ */
204
+ declare const loadConfig: (env?: NodeJS.ProcessEnv) => Config;
205
+ //#endregion
206
+ //#region src/client/dates.d.ts
207
+ /**
208
+ * What a tool reports for an event's start or end.
209
+ *
210
+ * A union rather than one struct with an `allDay` flag: an all-day event names
211
+ * a DAY and has no instant, and giving it an `iso` field would invite callers to
212
+ * read one. The type makes that impossible instead of merely discouraged.
213
+ */
214
+ type EventInstant = {
215
+ allDay: true;
216
+ day: string;
217
+ timeZone: null;
218
+ } | {
219
+ allDay: false;
220
+ iso: string;
221
+ timeZone: string | null;
222
+ };
223
+ //#endregion
224
+ //#region src/client/locate.d.ts
225
+ /**
226
+ * Find Calendar's store.
227
+ *
228
+ * ## Why this is the easy case
229
+ *
230
+ * Reminders keeps its database under a generated directory name, so resolving
231
+ * it means *listing* a protected directory — which is itself the privileged
232
+ * operation, leaving no path to even stat without the grant. Calendar does not:
233
+ *
234
+ * ~/Library/Group Containers/group.com.apple.calendar/Calendar.sqlitedb
235
+ *
236
+ * is a constant. `statSync` succeeds on a TCC-protected file (only `access(2)`
237
+ * is denied — see packages/core/src/fs.ts), so this locator can tell "exists but
238
+ * unreadable" from "not there at all" with no permission whatsoever. Those are
239
+ * different failures with different fixes, and saying so is most of what
240
+ * diagnostics is for.
241
+ *
242
+ * ## Why it still walks the container
243
+ *
244
+ * `docs/calendar.md` recorded per-account stores sitting beside the main one,
245
+ * and the probe picked between them by size. The known filename is *preferred*,
246
+ * so the common case costs one `describeStore` and no listing at all; the walk
247
+ * is a fallback for a machine whose layout differs, and for the day Apple moves
248
+ * the file the way it moved Reminders' out of `~/Library/Reminders`.
249
+ */
250
+ /** `group.com.apple.calendar`, under `~/Library/Group Containers`. */
251
+ declare const GROUP_CONTAINER = "group.com.apple.calendar";
252
+ /** The observed filename. Preferred when present; not required. */
253
+ declare const STORE_FILENAME = "Calendar.sqlitedb";
254
+ /** Sits beside the store. 32 KB, and its contents are not used by this server. */
255
+ declare const EXTRAS_FILENAME = "Extras.db";
256
+ type StoreCandidate = StoreFacts & {
257
+ path: string;
258
+ };
259
+ type LocateResult = StoreFacts & {
260
+ containerPath: string;
261
+ /** Null when discovery found nothing at all — see `reason`. */
262
+ storePath: string | null;
263
+ /** Every store-shaped file found, largest first. */
264
+ candidates: StoreCandidate[];
265
+ /**
266
+ * Whether the container could be listed.
267
+ *
268
+ * Unlike Reminders this is NOT the permission signal — the store's path is
269
+ * known, so `readable` answers that directly. It is reported because a
270
+ * listable container with no store in it means Calendar was never set up,
271
+ * which is a different conversation from a denied grant.
272
+ */
273
+ containerListable: boolean;
274
+ /** `Extras.db` beside the store. Recorded so it stops being an unknown. */
275
+ extrasPresent: boolean;
276
+ reason: string | null;
277
+ };
278
+ declare const defaultContainerPath: (home?: string) => string;
279
+ declare const defaultStorePath: (home?: string) => string;
280
+ declare const locateStore: (opts?: {
281
+ storePath?: string | undefined;
282
+ home?: string;
283
+ }) => LocateResult;
284
+ //#endregion
285
+ //#region src/client/recurrence.d.ts
286
+ type ExpansionState = "expanded" | "unavailable";
287
+ //#endregion
288
+ //#region src/client/calendar.d.ts
289
+ /**
290
+ * The lane orchestrator.
291
+ *
292
+ * Thinner than `packages/reminders`' by design. That surface arbitrates between
293
+ * two read lanes and caches an expensive Apple Events bulk fetch; Calendar has
294
+ * exactly one read lane, because `docs/distribution.md` sets the policy for new
295
+ * surfaces — file-lane reads, Apple Events for writes and live state — and
296
+ * `docs/calendar.md` measured why: a range query over Apple Events costs 3.4 s
297
+ * and does not improve with batching.
298
+ *
299
+ * So there is nothing to arbitrate. Either the store opens or the surface says
300
+ * plainly that it cannot answer, and never returns an empty list that reads like
301
+ * an empty calendar.
302
+ */
303
+ type LaneStatus = {
304
+ /**
305
+ * Always reported, never probed here.
306
+ *
307
+ * Reminders probes its Apple Events lane on every `lanes()` call because that
308
+ * lane answers reads. Calendar's does not: it exists only for writes, which
309
+ * are not implemented yet. Probing it would fire the Automation prompt for a
310
+ * capability the server does not currently have, which is a worse trade than
311
+ * saying so.
312
+ */
313
+ applescript: "not-used" | "live" | "unavailable";
314
+ index: "live" | "unavailable" | "disabled";
315
+ indexMode: string | null;
316
+ storeFingerprint: string | null;
317
+ reason: string | null;
318
+ };
319
+ type EventSummary = {
320
+ /** Opaque; feed it back to get_event or a write tool. */
321
+ ref: string;
322
+ summary: string | null;
323
+ start: EventInstant | null;
324
+ end: EventInstant | null;
325
+ allDay: boolean;
326
+ calendar: string | null;
327
+ location: string | null;
328
+ /** True when this row is one instance of a repeating event. */
329
+ isOccurrence: boolean;
330
+ /** Present on an occurrence: the ref naming the whole series. */
331
+ seriesRef?: string;
332
+ /** Raw store value, reported because the mapping above is inferred. */
333
+ status: number | null;
334
+ invitationStatus: number | null;
335
+ /** Which lane produced the row. */
336
+ source: EventRow["source"];
337
+ };
338
+ type EventDetail = EventSummary & {
339
+ description: string | null;
340
+ url: string | null;
341
+ conferenceUrl: string | null;
342
+ hasAttendees: boolean;
343
+ hasRecurrences: boolean;
344
+ timeZone: string | null;
345
+ };
346
+ type EventPage = {
347
+ events: EventSummary[];
348
+ expansion: ExpansionState;
349
+ expansionReason?: string;
350
+ /** The window actually queried, echoed so a clamp is visible. */
351
+ window: {
352
+ from: string;
353
+ to: string;
354
+ clamped: boolean;
355
+ };
356
+ coverage: {
357
+ from: string;
358
+ to: string;
359
+ rows: number;
360
+ } | null;
361
+ truncated?: {
362
+ reason: string;
363
+ affects: string;
364
+ uncoveredFrom?: string;
365
+ uncoveredTo?: string;
366
+ };
367
+ dropped?: number;
368
+ };
369
+ type EventFilters = {
370
+ from?: string | undefined;
371
+ to?: string | undefined;
372
+ calendar?: string | undefined;
373
+ includeDeclined?: boolean | undefined;
374
+ includeCancelled?: boolean | undefined;
375
+ limit: number;
376
+ };
377
+ type CreateClientOptions = {
378
+ config: Config;
379
+ logger?: Logger;
380
+ /** Injected by tests so nothing spawns a process or touches a real Calendar. */
381
+ osascript?: OsascriptRunner;
382
+ /** Injected by tests so a relative range resolves against a frozen clock. */
383
+ now?: () => Date;
384
+ };
385
+ /** What a write returns: what Calendar STORED, never what was requested. */
386
+ type WriteResult = {
387
+ ref: string;
388
+ uid: string | null;
389
+ summary: string | null;
390
+ start: string | null;
391
+ end: string | null;
392
+ allDay: boolean;
393
+ calendar: string | null;
394
+ /** Always "apple-events": writes never touch the store. */
395
+ source: "apple-events";
396
+ };
397
+ type CreateEventFields = {
398
+ summary: string;
399
+ calendar?: string | undefined;
400
+ start: string;
401
+ end?: string | undefined;
402
+ durationMinutes?: number | undefined;
403
+ allDay?: boolean | undefined;
404
+ location?: string | undefined;
405
+ description?: string | undefined;
406
+ url?: string | undefined;
407
+ };
408
+ type UpdateEventFields = {
409
+ ref: string;
410
+ summary?: string | undefined;
411
+ start?: string | undefined;
412
+ end?: string | undefined;
413
+ durationMinutes?: number | undefined;
414
+ allDay?: boolean | undefined;
415
+ location?: string | undefined;
416
+ description?: string | undefined;
417
+ url?: string | undefined;
418
+ };
419
+ declare class AppleCalendarClient {
420
+ #private;
421
+ readonly config: Config;
422
+ readonly runner: OsascriptRunner;
423
+ constructor(opts: CreateClientOptions);
424
+ /** Cached: the answer cannot change without the process being restarted anyway. */
425
+ locate(): LocateResult;
426
+ /**
427
+ * The store, opened lazily and at most once.
428
+ *
429
+ * Returns null rather than throwing, because "no index" is a state the caller
430
+ * has to render, not an exception. The reason lives on the locate result.
431
+ */
432
+ index(): CalendarStore | null;
433
+ /**
434
+ * Drop the open handle so the next read reopens.
435
+ *
436
+ * Called after a write: Calendar owns the store and reconciles it against a
437
+ * server, so an event created over Apple Events lands in the file on the
438
+ * app's schedule, not ours.
439
+ */
440
+ invalidate(): void;
441
+ listEvents(filters: EventFilters): EventPage;
442
+ /**
443
+ * Text search over events.
444
+ *
445
+ * Runs on items only. An occurrence carries no text of its own, so matching
446
+ * the series once is what a search result should be — expanding it here would
447
+ * bury one answer under fifty identical ones.
448
+ */
449
+ searchEvents(args: EventFilters & {
450
+ query: string;
451
+ scope?: "summary" | "full" | undefined;
452
+ }): EventPage;
453
+ getEvent(ref: string): EventDetail;
454
+ calendars(): IndexCalendar[];
455
+ accounts(): IndexAccount[];
456
+ createEvent(fields: CreateEventFields): Promise<WriteResult>;
457
+ updateEvent(fields: UpdateEventFields): Promise<WriteResult>;
458
+ /**
459
+ * Delete whole events.
460
+ *
461
+ * Only whole events: Calendar's scripting interface cannot remove a single
462
+ * occurrence of a repeating one. `excludedDates` — the property Calendar.app
463
+ * itself uses for "Delete This Event" — reads back a 1903 sentinel and throws
464
+ * on assignment, measured on macOS 26.6. So an occurrence ref is refused
465
+ * rather than silently deleting the whole series, which is the same shape of
466
+ * refusal `updateEvent` makes and for the same underlying reason.
467
+ */
468
+ deleteEvents(refs: readonly string[]): Promise<{
469
+ results: unknown[];
470
+ scope: string;
471
+ }>;
472
+ lanes(): LaneStatus;
473
+ }
474
+ //#endregion
475
+ //#region src/client/errors.d.ts
476
+ /**
477
+ * Named in every user-facing error and in the env vars they mention.
478
+ *
479
+ * `appName` is the display name, which is NOT the bundle id: Calendar.app is
480
+ * still `com.apple.iCal` underneath. Every other surface has the two agreeing,
481
+ * so the mismatch is written down in both places it matters.
482
+ */
483
+ declare const CALENDAR_SURFACE: SurfaceContext;
484
+ /** Calendar's Apple Events target. Not `com.apple.Calendar`, which does not exist. */
485
+ declare const CALENDAR_BUNDLE_ID = "com.apple.iCal";
486
+ /**
487
+ * A date argument could not be understood.
488
+ *
489
+ * Carries the accepted grammar rather than just rejecting, because the caller is
490
+ * usually a model that will retry once and needs to know what shape to retry in.
491
+ */
492
+ declare class InvalidDateError extends AppleAutomationError {
493
+ readonly name = "InvalidDateError";
494
+ constructor(field: string, raw: string, reason: string);
495
+ }
496
+ /** A CalendarRef no longer resolves — deleted, or moved to another calendar. */
497
+ declare class EventNotFoundError extends AppleAutomationError {
498
+ readonly name = "EventNotFoundError";
499
+ constructor(ref: string);
500
+ }
501
+ /** A calendar was named that Calendar does not have. */
502
+ declare class CalendarNotFoundError extends AppleAutomationError {
503
+ readonly name = "CalendarNotFoundError";
504
+ constructor(name: string, available?: readonly string[]);
505
+ }
506
+ /**
507
+ * A write was aimed at a calendar that cannot accept one.
508
+ *
509
+ * Its own error rather than a generic failure because the cause is almost
510
+ * always structural rather than a mistake: holiday, birthday and subscribed
511
+ * calendars are read-only by nature, and a caller that hits one needs to pick a
512
+ * different target, not retry.
513
+ */
514
+ declare class CalendarNotWritableError extends AppleAutomationError {
515
+ readonly name = "CalendarNotWritableError";
516
+ constructor(name: string);
517
+ }
518
+ //#endregion
519
+ //#region src/client/ref.d.ts
520
+ /**
521
+ * CalendarRef — the one identifier any tool accepts or returns.
522
+ *
523
+ * Wire format: c1:<calendarUid>/<occurrence>/<eventUid>
524
+ *
525
+ * calendarUid `Calendar.UUID`. Always a UUID in this store.
526
+ * occurrence "-" for a single event or a whole series, otherwise the
527
+ * occurrence start as ISO-8601 basic with offset,
528
+ * e.g. 20260821T090000+0200.
529
+ * eventUid `CalendarItem.UUID`, verbatim, as the greedy tail.
530
+ *
531
+ * The `c1:` prefix follows the same reasoning as Notes' `n1:` and Reminders'
532
+ * `r1:`: if the scheme ever changes, a versioned prefix makes that an additive
533
+ * change instead of a silent reinterpretation of every ref already sitting in a
534
+ * conversation.
535
+ *
536
+ * ## Why the uid is the greedy tail, and why `@` is not a separator
537
+ *
538
+ * `docs/calendar.md` measured the id bridge on an iCloud account, where every
539
+ * uid is a bare UUID. That is a property of the ACCOUNT, not of Calendar: a
540
+ * Google event's uid looks like `abc123def@google.com`, and an Exchange one is
541
+ * a long hex blob. Requiring a UUID here would work perfectly on the machine it
542
+ * was written on and fail completely on anyone else's — so the uid is carried
543
+ * through verbatim and the UUID is only extracted opportunistically.
544
+ *
545
+ * That also rules out `@` as a field separator, which is otherwise the obvious
546
+ * choice for pinning an occurrence to a time.
547
+ *
548
+ * ## Why the calendar uid rides along
549
+ *
550
+ * Calendar's scripting dictionary has no `events.byId()`. Finding an event over
551
+ * Apple Events means either `whose({uid})` — measured at 4.5-7.3 s and, worse,
552
+ * UNSTABLE across runs — or one bulk `cal.events.uid()` fetch and an index in
553
+ * JS, at about 1.8 s. The bulk fetch is only affordable if it is scoped to ONE
554
+ * calendar, so every write narrows by calendar before it scans.
555
+ *
556
+ * That is a concrete thing the file lane hands the write lane: the store knows
557
+ * which calendar an event is in, so Apple Events never has to search for it.
558
+ */
559
+ declare const REF_VERSION = "c1";
560
+ type CalendarRef = {
561
+ /** `Calendar.UUID` — which calendar to narrow to before scanning. */
562
+ calendarUid: string;
563
+ /** `CalendarItem.UUID`, exactly as stored. This is what resolves. */
564
+ eventUid: string;
565
+ /** Null for a single event or a whole series. */
566
+ occurrenceStart: Date | null;
567
+ /** True when this ref names one occurrence rather than the series. */
568
+ isOccurrence: boolean;
569
+ };
570
+ declare const encodeRef: (calendarUid: string, eventUid: string, occurrenceStart?: Date | null) => string;
571
+ declare const decodeRef: (raw: string) => CalendarRef;
572
+ /** The series a ref belongs to. Identity for a ref that is already a series. */
573
+ declare const seriesRefOf: (ref: CalendarRef) => string;
574
+ /** The bare UUID inside an id, when there is one. Null is legitimate, not an error. */
575
+ declare const uuidOf: (id: string) => string | null;
576
+ //#endregion
577
+ //#region src/server.d.ts
578
+ declare const SERVER_NAME: string;
579
+ declare const SERVER_VERSION: string;
580
+ type CreateServerOptions = {
581
+ config: Config;
582
+ logger?: Logger;
583
+ /** Injected by tests so nothing spawns a process or touches a real Calendar. */
584
+ osascript?: OsascriptRunner;
585
+ /** Injected by tests so a relative range resolves against a frozen clock. */
586
+ now?: () => Date;
587
+ };
588
+ type CreatedServer = {
589
+ server: McpServer;
590
+ client: AppleCalendarClient;
591
+ };
592
+ /**
593
+ * Build the server. Side-effect free: it opens no connection, spawns no
594
+ * process and reads no file, so a test can construct it freely and every
595
+ * external dependency arrives through an option.
596
+ *
597
+ */
598
+ declare const createServer: (opts: CreateServerOptions) => CreatedServer;
599
+ //#endregion
600
+ //#region src/tools/index.d.ts
601
+ type ToolContext = {
602
+ /**
603
+ * Register the mutating tools too. Off by default — with the flag off they are
604
+ * not merely refused, they are invisible and cannot be called at all.
605
+ */
606
+ allowWrites: boolean;
607
+ };
608
+ /**
609
+ * Register the Apple Calendar tools.
610
+ *
611
+ * The registered set is a pure function of `allowWrites` and nothing else. In
612
+ * particular it does NOT vary with whether Full Disk Access is granted: that is
613
+ * a runtime condition which can change while the process lives, and MCP clients
614
+ * cache the tool list, so a tool that appears and disappears would leave clients
615
+ * calling names the server no longer has. Tools that need the store instead
616
+ * report their source, or explain what is missing.
617
+ *
618
+ * Writes go through Apple Events, always. Not a preference: `PRAGMA query_only`
619
+ * is set on the store because Calendar owns it, holds it open and reconciles it
620
+ * against a server, so writing to it would corrupt sync state.
621
+ */
622
+ declare const registerTools: (server: McpServer, client: AppleCalendarClient, ctx: ToolContext) => void;
623
+ //#endregion
624
+ export { AppleCalendarClient, AppleCalendarError, BUILD_INFO, type BuildInfo, CALENDAR_BUNDLE_ID, CALENDAR_SURFACE, CalendarBusyError, CalendarNotFoundError, CalendarNotRunningError, CalendarNotWritableError, type CalendarRef, CalendarStore, type Config, type CreateClientOptions, type CreateServerOptions, EXTRAS_FILENAME, EventNotFoundError, GROUP_CONTAINER, InvalidDateError, type LaneStatus, type LocateResult, REF_VERSION, SERVER_NAME, SERVER_VERSION, STORE_FILENAME, type StoreCandidate, type StoreCapabilities, type ToolContext, createServer, decodeRef, defaultContainerPath, defaultStorePath, encodeRef, introspect, loadConfig, locateStore, openStore, registerTools, seriesRefOf, uuidOf };
625
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/build-info.ts","../src/client/store.ts","../src/config.ts","../src/client/dates.ts","../src/client/locate.ts","../src/client/recurrence.ts","../src/client/calendar.ts","../src/client/errors.ts","../src/client/ref.ts","../src/server.ts","../src/tools/index.ts"],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import { A as BUILD_INFO, C as CALENDAR_SURFACE, D as CalendarNotWritableError, E as CalendarNotRunningError, O as EventNotFoundError, S as CALENDAR_BUNDLE_ID, T as CalendarNotFoundError, _ as STORE_FILENAME, a as loadConfig, b as locateStore, c as introspect, d as decodeRef, f as encodeRef, g as GROUP_CONTAINER, h as EXTRAS_FILENAME, i as registerTools, k as InvalidDateError, l as openStore, m as uuidOf, n as SERVER_VERSION, o as AppleCalendarClient, p as seriesRefOf, r as createServer, s as CalendarStore, t as SERVER_NAME, u as REF_VERSION, v as defaultContainerPath, w as CalendarBusyError, x as AppleCalendarError, y as defaultStorePath } from "./server-B2HtiXLF.js";
2
+ export { AppleCalendarClient, AppleCalendarError, BUILD_INFO, CALENDAR_BUNDLE_ID, CALENDAR_SURFACE, CalendarBusyError, CalendarNotFoundError, CalendarNotRunningError, CalendarNotWritableError, CalendarStore, EXTRAS_FILENAME, EventNotFoundError, GROUP_CONTAINER, InvalidDateError, REF_VERSION, SERVER_NAME, SERVER_VERSION, STORE_FILENAME, createServer, decodeRef, defaultContainerPath, defaultStorePath, encodeRef, introspect, loadConfig, locateStore, openStore, registerTools, seriesRefOf, uuidOf };