@mgcrea/mcp-apple-messages 1.4.0 → 1.7.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 +31 -3
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +80 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{server-BSHulHkz.js → server-Bzgy1r6u.js} +350 -52
- package/dist/server-Bzgy1r6u.js.map +1 -0
- package/package.json +7 -6
- package/dist/server-BSHulHkz.js.map +0 -1
package/README.md
CHANGED
|
@@ -68,14 +68,25 @@ That is a capability downgrade reported through `diagnostics`, never a throw.
|
|
|
68
68
|
|
|
69
69
|
Read: `diagnostics`, `list_chats`, `list_messages`, `search_messages`, `get_message`.
|
|
70
70
|
|
|
71
|
+
Opt-in read: `find_codes`, behind `APPLE_MESSAGES_ALLOW_CODES` — see [Configuration](#configuration).
|
|
72
|
+
Off by default, and not covered by the write gate.
|
|
73
|
+
|
|
71
74
|
Write: `send_message`, and that is the whole dictionary. `sdef` lists three commands — `send`,
|
|
72
75
|
`login` and `logout` — and the other two would sign the user out of iMessage on every device they
|
|
73
76
|
own. There is no edit, delete, mark-as-read or reaction verb to expose, so **everything this server
|
|
74
77
|
can show you, it cannot change.**
|
|
75
78
|
|
|
76
|
-
`send`'s direct parameter is typed `file` OR `text
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
`send`'s direct parameter is typed `file` OR `text`, and both forms ship in two lanes with different
|
|
80
|
+
bounds. `attachmentId` forwards a file that is already in this Mac's Messages store — the same guid
|
|
81
|
+
`save_attachment` takes, through the same "inside `~/Library/Messages`, or refuse" boundary — so it
|
|
82
|
+
reaches no arbitrary path and needs no flag beyond `ALLOW_WRITES`. `filePath` names any local file,
|
|
83
|
+
which is an exfiltration primitive whose blast radius, unlike the text form's, is not bounded by what
|
|
84
|
+
the model can say; it exists as a parameter only when `APPLE_MESSAGES_ALLOW_FILE_SEND` is on, and it
|
|
85
|
+
is off by default. There is no directory confinement on it on purpose — see
|
|
86
|
+
[docs/messages.md](../../docs/messages.md) for why one would be a speed bump wearing a boundary's
|
|
87
|
+
clothes.
|
|
88
|
+
|
|
89
|
+
One call sends one thing, so a captioned photo is two calls.
|
|
79
90
|
|
|
80
91
|
### Sending, and how it reports what it sent
|
|
81
92
|
|
|
@@ -110,8 +121,25 @@ records exactly what that leaves open; the safe way to measure it is a message t
|
|
|
110
121
|
| `APPLE_MESSAGES_DEFAULT_RANGE_DAYS` | `30` | Window when only a start is given. |
|
|
111
122
|
| `APPLE_MESSAGES_MAX_RESULTS` | `50` | Default page size. |
|
|
112
123
|
| `APPLE_MESSAGES_ALLOW_WRITES` | off | Register `send_message` at all. |
|
|
124
|
+
| `APPLE_MESSAGES_ALLOW_CODES` | off | Register `find_codes` at all. |
|
|
125
|
+
| `APPLE_MESSAGES_ALLOW_FILE_SEND` | off | Add `filePath` to `send_message`. |
|
|
113
126
|
| `APPLE_MESSAGES_SEND_RECONCILE_MS` | `5000` | How long to wait for the sent row. |
|
|
114
127
|
|
|
128
|
+
`APPLE_MESSAGES_ALLOW_CODES` gates `find_codes`, which extracts one-time 2FA codes from recently
|
|
129
|
+
received messages. It is a **read**, and it is deliberately not folded into `ALLOW_WRITES`: reaching
|
|
130
|
+
a read through the write gate would mean granting the right to send a message in order to get it.
|
|
131
|
+
|
|
132
|
+
It is gated at all because of what it combines with. This server already holds the conversation
|
|
133
|
+
history and `@mgcrea/mcp-apple-mail` holds the inbox — between them, the password-_reset_ channel.
|
|
134
|
+
Adding live authentication codes completes an account-takeover primitive out of parts that were each
|
|
135
|
+
individually reasonable, so it defaults off and the two gates are independent in both directions.
|
|
136
|
+
|
|
137
|
+
Codes are matched by signal rather than by a `\d{4,8}` regex, and every result carries a
|
|
138
|
+
`confidence` and a `matched` saying how it was found; anything below `high` should be checked
|
|
139
|
+
against the message body before use. See `src/codes.ts` in `@mgcrea/mcp-apple-core` — the heuristic moved there when
|
|
140
|
+
`@mgcrea/mcp-apple-safari` became its second caller — and `docs/passwords.md` in the repo for
|
|
141
|
+
why the Passwords app itself is unreachable and this is what ships in its place.
|
|
142
|
+
|
|
115
143
|
## Notes that will bite you
|
|
116
144
|
|
|
117
145
|
- **Dates do not fit in a JavaScript number.** Every date column is nanoseconds since 2001 —
|
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-
|
|
2
|
+
import { A as MESSAGES_SURFACE, V as BUILD_INFO, a as loadConfig, r as createServer } from "./server-Bzgy1r6u.js";
|
|
3
3
|
import { runStdioServer } from "@mgcrea/mcp-apple-core";
|
|
4
4
|
//#region src/cli.ts
|
|
5
5
|
const LOG_PREFIX = "apple-messages-mcp";
|
package/dist/index.d.ts
CHANGED
|
@@ -40,6 +40,8 @@ declare const ConfigSchema: z.ZodObject<{
|
|
|
40
40
|
attachmentDir: z.ZodDefault<z.ZodString>;
|
|
41
41
|
defaultRangeDays: z.ZodDefault<z.ZodNumber>;
|
|
42
42
|
sendReconcileMs: z.ZodDefault<z.ZodNumber>;
|
|
43
|
+
allowCodes: z.ZodDefault<z.ZodBoolean>;
|
|
44
|
+
allowFileSend: z.ZodDefault<z.ZodBoolean>;
|
|
43
45
|
}, z.core.$strict>;
|
|
44
46
|
type Config = z.infer<typeof ConfigSchema>;
|
|
45
47
|
declare const loadConfig: (env?: NodeJS.ProcessEnv) => Config;
|
|
@@ -291,6 +293,13 @@ type CreateClientOptions = {
|
|
|
291
293
|
*/
|
|
292
294
|
type SendResult = {
|
|
293
295
|
sent: true;
|
|
296
|
+
/**
|
|
297
|
+
* What was sent: `text`, `attachment` (a file already in the Messages store,
|
|
298
|
+
* named by its guid) or `file` (a local path, which only exists as an input
|
|
299
|
+
* when `allowFileSend` is on). One call sends exactly one of them — the
|
|
300
|
+
* dictionary's direct parameter is file OR text.
|
|
301
|
+
*/
|
|
302
|
+
kind: "text" | "attachment" | "file";
|
|
294
303
|
/** Which rung of the ladder in `client/jxa/core.ts` answered. */
|
|
295
304
|
strategy: string;
|
|
296
305
|
targetKind: string;
|
|
@@ -375,16 +384,11 @@ declare class AppleMessagesClient {
|
|
|
375
384
|
*
|
|
376
385
|
* ## Two boundaries, not one
|
|
377
386
|
*
|
|
378
|
-
* The
|
|
379
|
-
*
|
|
380
|
-
*
|
|
381
|
-
*
|
|
382
|
-
*
|
|
383
|
-
* database this server never writes, and it is a fully-qualified path: taken
|
|
384
|
-
* at face value it names any file the process can read. So it is required to
|
|
385
|
-
* resolve inside the Messages root before a single byte is read. That check
|
|
386
|
-
* has never fired on a real store and is not expected to — it is here so that
|
|
387
|
-
* the day the schema surprises us, the surprise is a refusal.
|
|
387
|
+
* The SOURCE boundary is `#resolveStoreAttachment` above, shared with the send
|
|
388
|
+
* lane. What is left here is the DESTINATION boundary, the same one Mail and
|
|
389
|
+
* Notes enforce: `attachmentDir` is a confinement, `directory` may only select
|
|
390
|
+
* inside it, and the leaf name is `basename`d because it comes from whoever
|
|
391
|
+
* sent the message.
|
|
388
392
|
*/
|
|
389
393
|
saveAttachment(attachmentId: string, opts?: {
|
|
390
394
|
directory?: string | undefined;
|
|
@@ -400,12 +404,15 @@ declare class AppleMessagesClient {
|
|
|
400
404
|
* Send one message. A real one, to a real person, immediately.
|
|
401
405
|
*
|
|
402
406
|
* Everything difficult about this is in `client/jxa/core.ts`; what is left
|
|
403
|
-
* here is choosing the target from the file lane
|
|
407
|
+
* here is choosing the target from the file lane, deciding what may be sent,
|
|
408
|
+
* and reconciling afterwards.
|
|
404
409
|
*/
|
|
405
410
|
sendMessage(input: {
|
|
406
411
|
chatRef?: string | undefined;
|
|
407
412
|
to?: string | undefined;
|
|
408
|
-
text
|
|
413
|
+
text?: string | undefined;
|
|
414
|
+
attachmentId?: string | undefined;
|
|
415
|
+
filePath?: string | undefined;
|
|
409
416
|
service?: string | undefined;
|
|
410
417
|
}): Promise<SendResult>;
|
|
411
418
|
/** Bounds for a range query, as apple-seconds. */
|
|
@@ -696,11 +703,28 @@ declare const outline: (buffer: Uint8Array, maxTokens?: number) => string[];
|
|
|
696
703
|
* every device is not something to do behind a tool call, and there is no read
|
|
697
704
|
* to justify logging in.
|
|
698
705
|
*
|
|
699
|
-
* `send`'s direct parameter is typed `file` OR `text`. **
|
|
700
|
-
*
|
|
701
|
-
* transfers an arbitrary local path to a
|
|
702
|
-
* primitive, and unlike the text form its blast
|
|
703
|
-
* the model can say.
|
|
706
|
+
* `send`'s direct parameter is typed `file` OR `text`. **Both ship, in two lanes
|
|
707
|
+
* with different bounds.** This paragraph used to record the file form as
|
|
708
|
+
* deliberately omitted — a tool that transfers an arbitrary local path to a
|
|
709
|
+
* remote person is an exfiltration primitive, and unlike the text form its blast
|
|
710
|
+
* radius is not bounded by what the model can say. That reasoning is unchanged.
|
|
711
|
+
* What changed is that it turned out to argue against *one* of the two things
|
|
712
|
+
* a file send could mean, not both:
|
|
713
|
+
*
|
|
714
|
+
* - `attachmentId` forwards a file ALREADY in this Mac's Messages store,
|
|
715
|
+
* named by the same `attachment.guid` `save_attachment` takes. The client
|
|
716
|
+
* resolves it through the same source boundary — inside `messagesRoot`,
|
|
717
|
+
* nothing else — so there is no arbitrary path and no arbitrary read. The
|
|
718
|
+
* set is bounded by construction, which is why this lane ships under
|
|
719
|
+
* `allowWrites` alone.
|
|
720
|
+
* - `filePath` names any local file, and IS the primitive above. It is
|
|
721
|
+
* registered only when `allowFileSend` is on, and defaults off.
|
|
722
|
+
*
|
|
723
|
+
* The script below cannot tell the two apart and must not try: it receives one
|
|
724
|
+
* already-resolved `p.file` and forwards it. Every bound lives in the client,
|
|
725
|
+
* because a script that composed its own path would move the boundary somewhere
|
|
726
|
+
* nothing tests. `test/jxa.test.ts` pins that — one `Path(`, and its argument is
|
|
727
|
+
* `p.file`.
|
|
704
728
|
*
|
|
705
729
|
* ## The `to` parameter, and why the file lane picks the target
|
|
706
730
|
*
|
|
@@ -726,8 +750,17 @@ declare const PRELUDE = "\nObjC.import(\"AppKit\");\n\nfunction ok(data) { retur
|
|
|
726
750
|
* One script, one verb.
|
|
727
751
|
*
|
|
728
752
|
* `send` is the only mutating command in the Messages dictionary that this
|
|
729
|
-
* server exposes — see `core.ts` for the full list
|
|
730
|
-
* and the
|
|
753
|
+
* server exposes — see `core.ts` for the full list, for why `login`/`logout` are
|
|
754
|
+
* left out, and for the two lanes the file form arrives in.
|
|
755
|
+
*
|
|
756
|
+
* ## One call site, two payloads
|
|
757
|
+
*
|
|
758
|
+
* The file form is `M.send(Path(...), …)` and the text form is `M.send(str, …)`,
|
|
759
|
+
* which invites two branches around two `send` calls. It is written as one call
|
|
760
|
+
* over a payload chosen beforehand instead, because `test/jxa.test.ts` proves
|
|
761
|
+
* the target is resolved before anything is sent by comparing the position of
|
|
762
|
+
* `resolveTarget(` against `M.send(` — with two call sites that check would pin
|
|
763
|
+
* the first and quietly ignore the second.
|
|
731
764
|
*
|
|
732
765
|
* ## What this script deliberately does NOT do
|
|
733
766
|
*
|
|
@@ -745,7 +778,7 @@ declare const PRELUDE = "\nObjC.import(\"AppKit\");\n\nfunction ok(data) { retur
|
|
|
745
778
|
* re-resolve by scanning the store for a recent row on the target chat" — and it
|
|
746
779
|
* is what makes a send reportable at all on a surface with no id bridge.
|
|
747
780
|
*/
|
|
748
|
-
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 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(
|
|
781
|
+
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";
|
|
749
782
|
//#endregion
|
|
750
783
|
//#region src/server.d.ts
|
|
751
784
|
declare const SERVER_NAME: string;
|
|
@@ -788,6 +821,19 @@ type ToolContext = {
|
|
|
788
821
|
* survives it intact.
|
|
789
822
|
*/
|
|
790
823
|
allowWrites: boolean;
|
|
824
|
+
/**
|
|
825
|
+
* Gates `find_codes` alone, and is independent of `allowWrites` on purpose —
|
|
826
|
+
* see `config.ts` for why a read got a switch of its own.
|
|
827
|
+
*/
|
|
828
|
+
allowCodes: boolean;
|
|
829
|
+
/**
|
|
830
|
+
* Widens `send_message` with a `filePath` parameter, and is meaningless
|
|
831
|
+
* without `allowWrites` — see `config.ts`. It changes the tool's SCHEMA rather
|
|
832
|
+
* than the registered set, which is why it belongs here and not in a call-time
|
|
833
|
+
* check: a parameter that exists and is always refused is a parameter the
|
|
834
|
+
* model will keep filling in.
|
|
835
|
+
*/
|
|
836
|
+
allowFileSend: boolean;
|
|
791
837
|
};
|
|
792
838
|
/**
|
|
793
839
|
* Register the Apple Messages tools.
|
|
@@ -796,9 +842,20 @@ type ToolContext = {
|
|
|
796
842
|
* surface the flag carries a permission claim as well as a safety one: with it
|
|
797
843
|
* off no Apple Event is ever sent, so no Automation grant is ever requested.
|
|
798
844
|
* What is needed either way is Full Disk Access, absolutely — see `diagnostics`.
|
|
799
|
-
*
|
|
800
|
-
*
|
|
801
|
-
*
|
|
845
|
+
* One further read, `find_codes`, only when `allowCodes` is on.
|
|
846
|
+
*
|
|
847
|
+
* The registered set is a pure function of STATIC CONFIGURATION, never of
|
|
848
|
+
* runtime state. In particular it does NOT vary with whether the store is
|
|
849
|
+
* readable: MCP clients cache the tool list, so a set that shrank when a grant
|
|
850
|
+
* was missing would stay shrunk after the grant arrived.
|
|
851
|
+
*
|
|
852
|
+
* That invariant used to read "a pure function of `allowWrites` and nothing
|
|
853
|
+
* else". `allowCodes` widened the input without weakening the guarantee, which
|
|
854
|
+
* was always about runtime conditions rather than about there being exactly one
|
|
855
|
+
* flag. `allowFileSend` widens it again and is the first flag that changes a
|
|
856
|
+
* tool's SCHEMA rather than the registered set — the guarantee covers that too,
|
|
857
|
+
* for the same reason: an MCP client caches the shape as well as the list.
|
|
858
|
+
* `test/tools.test.ts` asserts each arm separately.
|
|
802
859
|
*/
|
|
803
860
|
declare const registerTools: (server: McpServer, client: AppleMessagesClient, ctx: ToolContext) => void;
|
|
804
861
|
//#endregion
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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":";;;;;;cAkBa,YAAY;;;;;;;;;;;;;;;;;;cCSnB,cAAY,EAAA
|
|
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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"}
|
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-
|
|
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-Bzgy1r6u.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 };
|