@mgcrea/mcp-apple-messages 1.30.0 → 1.34.0

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
@@ -126,17 +126,19 @@ records exactly what that leaves open; the safe way to measure it is a message t
126
126
 
127
127
  ## Configuration
128
128
 
129
- | Variable | Default | |
130
- | ----------------------------------- | ------- | --------------------------------------- |
131
- | `APPLE_MESSAGES_RESOLVE_CONTACTS` | on | Look names up in Contacts. |
132
- | `APPLE_MESSAGES_INDEX_MODE` | `auto` | `auto` \| `ro` \| `immutable` \| `off`. |
133
- | `APPLE_MESSAGES_STORE` | auto | Explicit store path. |
134
- | `APPLE_MESSAGES_DEFAULT_RANGE_DAYS` | `30` | Window when only a start is given. |
135
- | `APPLE_MESSAGES_MAX_RESULTS` | `50` | Default page size. |
136
- | `APPLE_MESSAGES_ALLOW_WRITES` | off | Register `send_message` at all. |
137
- | `APPLE_MESSAGES_ALLOW_CODES` | off | Register `find_codes` at all. |
138
- | `APPLE_MESSAGES_ALLOW_FILE_SEND` | off | Add `filePath` to `send_message`. |
139
- | `APPLE_MESSAGES_SEND_RECONCILE_MS` | `5000` | How long to wait for the sent row. |
129
+ | Variable | Default | |
130
+ | ----------------------------------- | ------- | ----------------------------------------------------------------------------------------------- |
131
+ | `APPLE_MESSAGES_RESOLVE_CONTACTS` | on | Look names up in Contacts. |
132
+ | `APPLE_MESSAGES_INDEX_MODE` | `auto` | `auto` \| `ro` \| `immutable` \| `off`. |
133
+ | `APPLE_MESSAGES_STORE` | auto | Explicit store path. |
134
+ | `APPLE_MESSAGES_DEFAULT_RANGE_DAYS` | `30` | Window when only a start is given. |
135
+ | `APPLE_MESSAGES_MAX_RESULTS` | `50` | Default page size. |
136
+ | `APPLE_MESSAGES_ALLOW_WRITES` | off | Register `send_message` at all. |
137
+ | `APPLE_MESSAGES_ALLOW_CODES` | off | Register `find_codes` at all. |
138
+ | `APPLE_MESSAGES_ALLOW_WATCHES` | off | Register `watch` and `unwatch`. |
139
+ | `APPLE_MESSAGES_ALLOW_EVENTS` | off | Serve the experimental `messages.received` event (draft MCP Events extension, 2026-07-28 only). |
140
+ | `APPLE_MESSAGES_ALLOW_FILE_SEND` | off | Add `filePath` to `send_message`. |
141
+ | `APPLE_MESSAGES_SEND_RECONCILE_MS` | `5000` | How long to wait for the sent row. |
140
142
 
141
143
  `APPLE_MESSAGES_ALLOW_CODES` gates `find_codes`, which extracts one-time 2FA codes from recently
142
144
  received messages. It is a **read**, and it is deliberately not folded into `ALLOW_WRITES`: reaching
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { A as MESSAGES_SURFACE, V as BUILD_INFO, a as loadConfig, r as createServer } from "./server-rYfdOV2M.js";
2
+ import { A as MESSAGES_SURFACE, V as BUILD_INFO, a as loadConfig, r as createServer } from "./server-D-2jgV-B.js";
3
3
  import { runStdioServer } from "@mgcrea/mcp-apple-core";
4
4
  //#region src/cli.ts
5
5
  const LOG_PREFIX = "apple-messages-mcp";
@@ -7,16 +7,17 @@ runStdioServer({
7
7
  build: BUILD_INFO,
8
8
  surface: MESSAGES_SURFACE,
9
9
  logPrefix: LOG_PREFIX,
10
- start: async (logger) => {
10
+ start: async (logger, ctx) => {
11
11
  const config = loadConfig();
12
12
  const { server, client } = createServer({
13
13
  config,
14
- logger
14
+ logger,
15
+ era: ctx.era
15
16
  });
16
17
  const status = client.status();
17
18
  return {
18
19
  server,
19
- banner: `writes=${config.allowWrites ? "ENABLED" : "disabled"}, store=${status.store.opened ? "open" : "UNREADABLE"}, messages=${status.counts?.messages ?? 0}, chats=${status.counts?.chats ?? 0}, names=${status.contacts.available ? "contacts" : "raw handles"}, index=${config.indexMode}`
20
+ banner: `writes=${config.allowWrites ? "ENABLED" : "disabled"}, watches=${config.allowWatches ? "ENABLED" : "disabled"}, store=${status.store.opened ? "open" : "UNREADABLE"}, messages=${status.counts?.messages ?? 0}, chats=${status.counts?.chats ?? 0}, names=${status.contacts.available ? "contacts" : "raw handles"}, index=${config.indexMode}`
20
21
  };
21
22
  }
22
23
  }).catch((err) => {
package/dist/cli.js.map CHANGED
@@ -1 +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 { MESSAGES_SURFACE } from \"./client/errors.js\";\nimport { loadConfig } from \"./config.js\";\nimport { createServer } from \"./server.js\";\n\nconst LOG_PREFIX = \"apple-messages-mcp\";\n\nrunStdioServer({\n build: BUILD_INFO,\n surface: MESSAGES_SURFACE,\n logPrefix: LOG_PREFIX,\n start: async (logger) => {\n const config = loadConfig();\n const { server, client } = createServer({ config, logger });\n const status = client.status();\n return {\n server,\n banner:\n `writes=${config.allowWrites ? \"ENABLED\" : \"disabled\"}, ` +\n `store=${status.store.opened ? \"open\" : \"UNREADABLE\"}, ` +\n `messages=${status.counts?.messages ?? 0}, ` +\n `chats=${status.counts?.chats ?? 0}, ` +\n `names=${status.contacts.available ? \"contacts\" : \"raw handles\"}, ` +\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,QAAQ,WAAW,aAAa;GAAE;GAAQ;EAAO,CAAC;EAC1D,MAAM,SAAS,OAAO,OAAO;EAC7B,OAAO;GACL;GACA,QACE,UAAU,OAAO,cAAc,YAAY,WAAW,UAC7C,OAAO,MAAM,SAAS,SAAS,aAAa,aACzC,OAAO,QAAQ,YAAY,EAAE,UAChC,OAAO,QAAQ,SAAS,EAAE,UAC1B,OAAO,SAAS,YAAY,aAAa,cAAc,UACvD,OAAO;EACpB;CACF;AACF,CAAC,CAAC,CAAC,OAAO,QAAiB;CACzB,QAAQ,MAAM,IAAI,WAAW,WAAW,GAAG;CAC3C,QAAQ,KAAK,CAAC;AAChB,CAAC"}
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 { MESSAGES_SURFACE } from \"./client/errors.js\";\nimport { loadConfig } from \"./config.js\";\nimport { createServer } from \"./server.js\";\n\nconst LOG_PREFIX = \"apple-messages-mcp\";\n\nrunStdioServer({\n build: BUILD_INFO,\n surface: MESSAGES_SURFACE,\n logPrefix: LOG_PREFIX,\n start: async (logger, ctx) => {\n const config = loadConfig();\n const { server, client } = createServer({ config, logger, era: ctx.era });\n const status = client.status();\n return {\n server,\n banner:\n `writes=${config.allowWrites ? \"ENABLED\" : \"disabled\"}, ` +\n `watches=${config.allowWatches ? \"ENABLED\" : \"disabled\"}, ` +\n `store=${status.store.opened ? \"open\" : \"UNREADABLE\"}, ` +\n `messages=${status.counts?.messages ?? 0}, ` +\n `chats=${status.counts?.chats ?? 0}, ` +\n `names=${status.contacts.available ? \"contacts\" : \"raw handles\"}, ` +\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,QAAQ,QAAQ;EAC5B,MAAM,SAAS,WAAW;EAC1B,MAAM,EAAE,QAAQ,WAAW,aAAa;GAAE;GAAQ;GAAQ,KAAK,IAAI;EAAI,CAAC;EACxE,MAAM,SAAS,OAAO,OAAO;EAC7B,OAAO;GACL;GACA,QACE,UAAU,OAAO,cAAc,YAAY,WAAW,YAC3C,OAAO,eAAe,YAAY,WAAW,UAC/C,OAAO,MAAM,SAAS,SAAS,aAAa,aACzC,OAAO,QAAQ,YAAY,EAAE,UAChC,OAAO,QAAQ,SAAS,EAAE,UAC1B,OAAO,SAAS,YAAY,aAAa,cAAc,UACvD,OAAO;EACpB;CACF;AACF,CAAC,CAAC,CAAC,OAAO,QAAiB;CACzB,QAAQ,MAAM,IAAI,WAAW,WAAW,GAAG;CAC3C,QAAQ,KAAK,CAAC;AAChB,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { AppleAutomationError, AppleAutomationError as AppleMessagesError, BuildInfo, CORE_DATA_EPOCH_OFFSET, IndexUnavailableError, Logger, OsascriptRunner, ReadOnlyMode, SchemaDriftError, StoreFacts, SurfaceContext } from "@mgcrea/mcp-apple-core";
1
+ import { AppleAutomationError, AppleAutomationError as AppleMessagesError, ArrivalRow, ArrivalSource, BuildInfo, CORE_DATA_EPOCH_OFFSET, Era, FileIdentity, IndexUnavailableError, Logger, OsascriptRunner, ReadOnlyMode, SchemaDriftError, StoreFacts, SurfaceContext, Watch, WatchRegistry } from "@mgcrea/mcp-apple-core";
2
2
  import { AppleContactsClient } from "@mgcrea/mcp-apple-contacts";
3
3
  import { z } from "zod";
4
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { McpServer } from "@modelcontextprotocol/server";
5
5
  import { DatabaseSync } from "node:sqlite";
6
6
  //#region src/build-info.d.ts
7
7
  declare const BUILD_INFO: BuildInfo;
@@ -42,6 +42,8 @@ declare const ConfigSchema: z.ZodObject<{
42
42
  sendReconcileMs: z.ZodDefault<z.ZodNumber>;
43
43
  allowCodes: z.ZodDefault<z.ZodBoolean>;
44
44
  allowFileSend: z.ZodDefault<z.ZodBoolean>;
45
+ allowWatches: z.ZodDefault<z.ZodBoolean>;
46
+ allowEvents: z.ZodDefault<z.ZodBoolean>;
45
47
  }, z.core.$strict>;
46
48
  type Config = z.infer<typeof ConfigSchema>;
47
49
  declare const loadConfig: (env?: NodeJS.ProcessEnv) => Config;
@@ -206,6 +208,26 @@ declare class MessagesStore {
206
208
  * `Liked "see you at 8"` — which is not something anybody typed.
207
209
  */
208
210
  range(q: RangeQuery): MessageRow[];
211
+ /** Highest message ROWID now, or 0 for an empty store. The watch cursor's head. */
212
+ headRowid(): number;
213
+ /**
214
+ * Messages with ROWID above `after`, ascending, at most `limit`.
215
+ *
216
+ * Nothing is filtered out, reactions and my own messages included: a watch
217
+ * cursor advances over every ROWID and the caller decides what is excluded.
218
+ * `message` is AUTOINCREMENT, so a ROWID is never handed out twice and the
219
+ * cursor can only move forward.
220
+ *
221
+ * Exactly one row per ROWID. `#joins` yields a row per chat for a message in
222
+ * several, which would give one cursor two rows and spend two slots of a read's
223
+ * limit on one message, so the chat here is picked: the lowest `chat_id`.
224
+ *
225
+ * No text: a watch carries metadata only, so `text` is always null here and
226
+ * the scan never pays for decoding a body.
227
+ */
228
+ arrivalsAfter(after: number, limit: number): (MessageRow & {
229
+ rowid: number;
230
+ })[];
209
231
  /**
210
232
  * Text search, in two passes — and the second one is the point.
211
233
  *
@@ -505,6 +527,21 @@ declare class AppleMessagesClient {
505
527
  totalGroups: number;
506
528
  totalRows: number;
507
529
  };
530
+ /**
531
+ * The stored spellings that name the same correspondent as `typed`. Candidates,
532
+ * not answers: a locally-spelled number reaches `+33…` here, so a caller that
533
+ * needs an exact spelling still has to compare. `[]` when the store is closed.
534
+ * `pool` is the store's handles when the caller already read them.
535
+ */
536
+ storeHandlesFor(typed: string, pool?: readonly string[]): string[];
537
+ /**
538
+ * The open store and the file it came from, for a watch. Throws the same
539
+ * reason every other read throws when the store is off or unreadable.
540
+ */
541
+ watchStore(): {
542
+ store: MessagesStore;
543
+ path: string;
544
+ };
508
545
  /**
509
546
  * Send one message. A real one, to a real person, immediately.
510
547
  *
@@ -622,13 +659,12 @@ declare class SendFailedError extends AppleAutomationError {
622
659
  *
623
660
  * ## Why a GUID rather than a rowid
624
661
  *
625
- * Both exist, and the rowid is faster to look up. The GUID wins anyway because
626
- * **rowids are reused.** SQLite hands a deleted row's id to the next insert
627
- * unless the table is `AUTOINCREMENT`, and Messages deletes constantly — every
628
- * "delete this conversation" frees a block of them. A ref handed to a model in
629
- * one turn and used two turns later would then resolve to a DIFFERENT message,
630
- * with no error anywhere. That is the failure this project keeps designing
631
- * against: plausible, wrong, and silent.
662
+ * Both exist, and the rowid is faster to look up. The GUID wins anyway, and not
663
+ * because rowids are reused: `message` is `AUTOINCREMENT`, so a deleted row's id
664
+ * is never handed to a later insert (the watch cursor relies on exactly that,
665
+ * and `watch.test.ts` pins it). A rowid is a position in this one database file.
666
+ * It means nothing on another device or after a sync rebuilds the store, whereas
667
+ * the GUID is the message's own identity and travels with it.
632
668
  *
633
669
  * The GUID is also what Apple itself joins on — `associated_message_guid` for
634
670
  * reactions, `thread_originator_guid` for replies — so it is the identifier the
@@ -885,6 +921,106 @@ declare const PRELUDE = "\nObjC.import(\"AppKit\");\n\nfunction ok(data) { retur
885
921
  */
886
922
  declare const SEND_MESSAGE = "\nObjC.import(\"AppKit\");\n\nfunction ok(data) { return JSON.stringify({ ok: true, data: data }); }\nfunction err(code, message, extra) {\n var e = { code: code, message: String(message) };\n if (extra) e.detail = extra;\n return JSON.stringify({ ok: false, error: e });\n}\n\n/** Read one property defensively — every read on this surface is allowed to fail. */\nfunction prop(fn, fallback) {\n try {\n var v = fn();\n return v === undefined ? fallback : v;\n } catch (e) {\n return fallback;\n }\n}\n\nfunction isMessagesRunning() {\n var apps = $.NSRunningApplication.runningApplicationsWithBundleIdentifier(\"com.apple.MobileSMS\");\n return apps.count > 0;\n}\n\n/** The dictionary's service type enumeration: SMS, iMessage, RCS. */\nfunction serviceEnum(name) {\n var s = String(name || \"\").toLowerCase();\n if (s === \"sms\") return \"SMS\";\n if (s === \"rcs\") return \"RCS\";\n return \"iMessage\";\n}\n\n/**\n * Find something `send` will accept as its `to`.\n *\n * Ordered cheapest and most-likely-to-work first. Every rung is wrapped, every\n * failure is recorded, and the caller is told which one answered — on a surface\n * whose whole read half is known broken, \"it worked\" without \"how\" is not a\n * result anyone can act on later.\n */\nfunction resolveTarget(M, p, tried) {\n var i;\n\n // 1. The chat guid the file lane read out of chat.db. No enumeration.\n if (p.chatGuid) {\n try {\n var chat = M.chats.byId(p.chatGuid);\n chat.id();\n return { target: chat, strategy: \"chat-guid\", kind: \"chat\" };\n } catch (e) {\n tried.push(\"chat-guid: \" + (e.message || e));\n }\n }\n\n if (!p.handle) return null;\n\n // 2. The guid Messages composes for a one-to-one chat, spelled the way the\n // store spells it: \"iMessage;-;+15551234567\". Constructed rather than read,\n // so it is below the real one and above everything that enumerates.\n var services = p.service ? [serviceEnum(p.service)] : [\"iMessage\", \"SMS\", \"RCS\"];\n for (i = 0; i < services.length; i++) {\n var guess = services[i] + \";-;\" + p.handle;\n try {\n var guessed = M.chats.byId(guess);\n guessed.id();\n return { target: guessed, strategy: \"chat-guid-guess\", kind: \"chat\", guid: guess };\n } catch (e2) {\n tried.push(\"chat-guid-guess(\" + guess + \"): \" + (e2.message || e2));\n }\n }\n\n // 3. A participant reached through its account. This enumerates, so it is\n // expected to fail with \"Application isn't running\" — kept because it is\n // the form every AppleScript example on the internet uses, and because if\n // launching the app does wake the scripting interface, this is what works.\n for (i = 0; i < services.length; i++) {\n try {\n var accounts = M.accounts.whose({ serviceType: services[i] })();\n for (var j = 0; j < accounts.length; j++) {\n try {\n var buddy = accounts[j].participants.whose({ handle: p.handle })()[0];\n if (buddy) {\n buddy.id();\n return { target: buddy, strategy: \"account-participant\", kind: \"participant\" };\n }\n } catch (e4) {\n tried.push(\"account-participant(\" + services[i] + \"): \" + (e4.message || e4));\n }\n }\n } catch (e3) {\n tried.push(\"accounts(\" + services[i] + \"): \" + (e3.message || e3));\n }\n }\n\n // 4. The flat participant list, last because it is the widest enumeration.\n try {\n var flat = M.participants.whose({ handle: p.handle })()[0];\n if (flat) {\n flat.id();\n return { target: flat, strategy: \"participant\", kind: \"participant\" };\n }\n tried.push(\"participant: no participant with that handle\");\n } catch (e5) {\n tried.push(\"participant: \" + (e5.message || e5));\n }\n\n return null;\n}\n\nfunction run(argv) {\n var p = JSON.parse(argv[0]);\n var M = Application(\"Messages\");\n\n var wasRunning = isMessagesRunning();\n if (!wasRunning && !p.allowLaunch) {\n return err(\"APP_NOT_RUNNING\", \"Messages is not running.\");\n }\n\n // The dictionary's direct parameter is file OR text, so one send sends one\n // thing. The client enforces this too; it is repeated here because a\n // malformed param set must fail before a target is addressed, not after.\n var hasFile = typeof p.file === \"string\" && p.file.length > 0;\n var hasText = typeof p.text === \"string\" && p.text.length > 0;\n if (hasFile === hasText) {\n return err(\"SEND_PAYLOAD_INVALID\", \"Expected exactly one of text or file.\");\n }\n // `p.file` is already resolved and already bounded by the client. This does\n // not build a path, join one, or read one — it forwards what it was handed.\n var payload = hasFile ? Path(p.file) : p.text;\n\n var tried = [];\n var resolved = resolveTarget(M, p, tried);\n if (!resolved) {\n return err(\n \"SEND_TARGET_NOT_FOUND\",\n \"Messages would not resolve a chat or participant for that recipient.\",\n tried\n );\n }\n\n try {\n M.send(payload, { to: resolved.target });\n } catch (e) {\n return err(\"SEND_FAILED\", e.message || e, tried);\n }\n\n return ok({\n sent: hasFile ? \"file\" : \"text\",\n strategy: resolved.strategy,\n targetKind: resolved.kind,\n // Best effort, and allowed to be null: reading the id back is itself a read.\n targetId: prop(function () { return String(resolved.target.id()); }, null),\n launched: !wasRunning,\n attempts: tried\n });\n}\n";
887
923
  //#endregion
924
+ //#region src/client/watch.d.ts
925
+ /**
926
+ * Messages' arrival source over chat.db.
927
+ *
928
+ * Metadata only: a `MessageArrival` has no `text`, even when the row's column is
929
+ * set. The body stays behind `apple_messages_get_message`.
930
+ *
931
+ * `message` is AUTOINCREMENT, so the ROWID is the cursor and never repeats; `key`
932
+ * is the guid, which is also what the public ref carries.
933
+ *
934
+ * What the source drops for every watch (`excluded`): my own messages,
935
+ * tapbacks, and group events (`item_type` other than 0: a rename, someone added
936
+ * or leaving), which are rows from a handle that nobody typed. Messages has no
937
+ * per-watch scope, so `scope` stays undefined.
938
+ */
939
+ /** No `text`: a message's text is its body, and watch reads carry metadata only. */
940
+ type MessageArrival = ArrivalRow & {
941
+ /** The message's public ref, as `get_message` and search emit it. */
942
+ id: string;
943
+ /** The chat's public ref, as listing tools emit it. */
944
+ chat: string | null;
945
+ handle: string | null;
946
+ /** As search and range render it; null when the row has no date. */
947
+ date: string | null;
948
+ };
949
+ //#endregion
950
+ //#region src/tools/watch.d.ts
951
+ type MessagesWatchServiceOptions = {
952
+ client: AppleMessagesClient;
953
+ logger?: Logger | undefined;
954
+ /**
955
+ * Who reads through the service: watches (the default), or the Events
956
+ * extension. Only the wording of a refusal depends on it, so an events client
957
+ * reads "subscription" where a watch's would read "watch".
958
+ */
959
+ mode?: "watch" | "events" | undefined;
960
+ };
961
+ declare class MessagesWatchService {
962
+ #private;
963
+ /** What `createWatches` is given. Delegates to the source `ensure()` builds. */
964
+ readonly source: ArrivalSource<MessageArrival>;
965
+ constructor(opts: MessagesWatchServiceOptions);
966
+ /** How many watches this connection holds. */
967
+ get count(): number;
968
+ /** Called once, by `createServer`, after `createWatches` returns. */
969
+ bind(registry: WatchRegistry<MessageArrival>): void;
970
+ /**
971
+ * Open the store and build the source, once. Concurrency-safe: two simultaneous
972
+ * `watch` calls share the promise. A failure is not kept, so a later call runs
973
+ * setup again.
974
+ */
975
+ ensure(): Promise<void>;
976
+ /**
977
+ * `ensure`, then reopen chat.db if it was replaced since. What an events poll
978
+ * or tick runs before it reads: a poll has no trigger to notice a rebuild for
979
+ * it, and without this would read the deleted file for as long as the
980
+ * connection lives. `ensure` itself does not reopen, so watches are unchanged.
981
+ */
982
+ refresh(): Promise<void>;
983
+ /** What `createWatches` is given as `startTrigger`. Reads the directory now, not at construction. */
984
+ startTrigger: (onChange: () => void) => {
985
+ stop(): void;
986
+ };
987
+ /** The trigger's callback: reopen a replaced store, then tick. */
988
+ onTrigger(tick: () => void): void;
989
+ /** The file identity of the chat.db the service reads now, or null when it is unknown or unreadable. */
990
+ identity(): FileIdentity | null;
991
+ /** Close the service's own handle. Called when the connection ends. */
992
+ close(): void;
993
+ remove(id: string): boolean;
994
+ /**
995
+ * Turn what the caller typed into exact store spellings.
996
+ *
997
+ * `storeHandlesFor` is a candidate list (a local "06 12 ..." reaches "+33 6 12 ..."),
998
+ * so a typed value counts as resolved only when a candidate equals it after
999
+ * normalisation. A short "+336" therefore resolves nothing, rather than every
1000
+ * French number. What does not resolve is kept, normalised and lowercased, and
1001
+ * reported (with `suggestions` when the store holds near misses).
1002
+ */
1003
+ resolveFrom(typed: readonly string[]): {
1004
+ from: string[];
1005
+ unresolved: string[];
1006
+ suggestions: {
1007
+ typed: string;
1008
+ candidates: string[];
1009
+ }[];
1010
+ };
1011
+ add(input: {
1012
+ from?: string[] | undefined;
1013
+ domains?: string[] | undefined;
1014
+ }): Promise<{
1015
+ watch: Watch;
1016
+ unresolved: string[];
1017
+ suggestions: {
1018
+ typed: string;
1019
+ candidates: string[];
1020
+ }[];
1021
+ }>;
1022
+ }
1023
+ //#endregion
888
1024
  //#region src/server.d.ts
889
1025
  declare const SERVER_NAME: string;
890
1026
  declare const SERVER_VERSION: string;
@@ -897,10 +1033,16 @@ type CreateServerOptions = {
897
1033
  home?: string;
898
1034
  /** Injected by tests, so no test reaches the developer's real address book. */
899
1035
  contacts?: ConstructorParameters<typeof AppleMessagesClient>[0]["contacts"];
1036
+ /** Which protocol era this instance serves. Only watches care. Default `legacy`. */
1037
+ era?: Era;
900
1038
  };
901
1039
  type CreatedServer = {
902
1040
  server: McpServer;
903
1041
  client: AppleMessagesClient;
1042
+ /** Present only with `allowWatches` on. */
1043
+ watches?: MessagesWatchService | undefined;
1044
+ /** Present only with `allowEvents` on. Its own service, never the watches'. */
1045
+ events?: MessagesWatchService | undefined;
904
1046
  };
905
1047
  /**
906
1048
  * Build the server. Side-effect free: it opens no database and reads no file,
@@ -939,6 +1081,11 @@ type ToolContext = {
939
1081
  * model will keep filling in.
940
1082
  */
941
1083
  allowFileSend: boolean;
1084
+ /**
1085
+ * Present only when `allowWatches` is on. Independent of `allowWrites`: both
1086
+ * watch tools are reads, so they register on read-only servers too.
1087
+ */
1088
+ watches?: MessagesWatchService | undefined;
942
1089
  };
943
1090
  /**
944
1091
  * Register the Apple Messages tools.
@@ -947,7 +1094,8 @@ type ToolContext = {
947
1094
  * surface the flag carries a permission claim as well as a safety one: with it
948
1095
  * off no Apple Event is ever sent, so no Automation grant is ever requested.
949
1096
  * What is needed either way is Full Disk Access, absolutely — see `diagnostics`.
950
- * One further read, `find_codes`, only when `allowCodes` is on.
1097
+ * One further read, `find_codes`, only when `allowCodes` is on. Two more, the watch
1098
+ * tools, only when `allowWatches` is on (`ctx.watches`).
951
1099
  *
952
1100
  * The registered set is a pure function of STATIC CONFIGURATION, never of
953
1101
  * runtime state. In particular it does NOT vary with whether the store is
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/build-info.ts","../src/config.ts","../src/client/locate.ts","../src/client/store.ts","../src/client/messages.ts","../src/client/dates.ts","../src/client/errors.ts","../src/client/ref.ts","../src/client/typedstream.ts","../src/client/jxa/core.ts","../src/client/jxa/write.ts","../src/server.ts","../src/tools/index.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/build-info.ts","../src/config.ts","../src/client/locate.ts","../src/client/store.ts","../src/client/messages.ts","../src/client/dates.ts","../src/client/errors.ts","../src/client/ref.ts","../src/client/typedstream.ts","../src/client/jxa/core.ts","../src/client/jxa/write.ts","../src/client/watch.ts","../src/tools/watch.ts","../src/server.ts","../src/tools/index.ts"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { A as MESSAGES_SURFACE, B as toAppleSeconds, C as locateStore, D as ChatNotFoundError, E as AppleMessagesError, F as SendTargetNotFoundError, I as CORE_DATA_EPOCH_OFFSET, L as appleSecondsSql, M as MessagesUnavailableError, N as SchemaDriftError, O as IndexUnavailableError, P as SendFailedError, R as fromAppleSeconds, S as defaultStorePath, T as PRELUDE, V as BUILD_INFO, _ as decodeMessageRef, a as loadConfig, b as ATTACHMENTS_RELATIVE, c as introspect, d as decodeAttributedBody, f as outline, g as decodeChatRef, h as MESSAGE_REF_VERSION, i as registerTools, j as MessageNotFoundError, k as MESSAGES_BUNDLE_ID, l as openStore, m as InvalidMessageRefError, n as SERVER_VERSION, o as AppleMessagesClient, p as CHAT_REF_VERSION, r as createServer, s as MessagesStore, t as SERVER_NAME, u as reactionLabel, v as encodeChatRef, w as SEND_MESSAGE, x as STORE_RELATIVE, y as encodeMessageRef, z as renderInstant } from "./server-rYfdOV2M.js";
1
+ import { A as MESSAGES_SURFACE, B as toAppleSeconds, C as locateStore, D as ChatNotFoundError, E as AppleMessagesError, F as SendTargetNotFoundError, I as CORE_DATA_EPOCH_OFFSET, L as appleSecondsSql, M as MessagesUnavailableError, N as SchemaDriftError, O as IndexUnavailableError, P as SendFailedError, R as fromAppleSeconds, S as defaultStorePath, T as PRELUDE, V as BUILD_INFO, _ as decodeMessageRef, a as loadConfig, b as ATTACHMENTS_RELATIVE, c as introspect, d as decodeAttributedBody, f as outline, g as decodeChatRef, h as MESSAGE_REF_VERSION, i as registerTools, j as MessageNotFoundError, k as MESSAGES_BUNDLE_ID, l as openStore, m as InvalidMessageRefError, n as SERVER_VERSION, o as AppleMessagesClient, p as CHAT_REF_VERSION, r as createServer, s as MessagesStore, t as SERVER_NAME, u as reactionLabel, v as encodeChatRef, w as SEND_MESSAGE, x as STORE_RELATIVE, y as encodeMessageRef, z as renderInstant } from "./server-D-2jgV-B.js";
2
2
  export { ATTACHMENTS_RELATIVE, AppleMessagesClient, AppleMessagesError, BUILD_INFO, CHAT_REF_VERSION, CORE_DATA_EPOCH_OFFSET, ChatNotFoundError, IndexUnavailableError, InvalidMessageRefError, MESSAGES_BUNDLE_ID, MESSAGES_SURFACE, MESSAGE_REF_VERSION, MessageNotFoundError, MessagesStore, MessagesUnavailableError, PRELUDE, SEND_MESSAGE, SERVER_NAME, SERVER_VERSION, STORE_RELATIVE, SchemaDriftError, SendFailedError, SendTargetNotFoundError, appleSecondsSql, createServer, decodeAttributedBody, decodeChatRef, decodeMessageRef, defaultStorePath, encodeChatRef, encodeMessageRef, fromAppleSeconds, introspect, loadConfig, locateStore, openStore, outline, reactionLabel, registerTools, renderInstant, toAppleSeconds };