@zyx1121/apple-mail-mcp 0.1.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/LICENSE +21 -0
- package/README.md +47 -0
- package/dist/applescript.d.ts +5 -0
- package/dist/applescript.js +29 -0
- package/dist/applescript.js.map +1 -0
- package/dist/helpers.d.ts +19 -0
- package/dist/helpers.js +25 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +15 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/accounts.d.ts +2 -0
- package/dist/tools/accounts.js +62 -0
- package/dist/tools/accounts.js.map +1 -0
- package/dist/tools/messages.d.ts +2 -0
- package/dist/tools/messages.js +160 -0
- package/dist/tools/messages.js.map +1 -0
- package/dist/tools/search.d.ts +2 -0
- package/dist/tools/search.js +80 -0
- package/dist/tools/search.js.map +1 -0
- package/package.json +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 zyx1121
|
|
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,47 @@
|
|
|
1
|
+
# @zyx1121/apple-mail-mcp
|
|
2
|
+
|
|
3
|
+
MCP server for Apple Mail — read, search, and manage emails via Claude Code.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
claude mcp add apple-mail -- npx @zyx1121/apple-mail-mcp
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Prerequisites
|
|
12
|
+
|
|
13
|
+
- macOS with Apple Mail configured
|
|
14
|
+
- Node.js >= 18
|
|
15
|
+
- First run will prompt for Automation permission (System Settings > Privacy & Security > Automation)
|
|
16
|
+
|
|
17
|
+
## Tools
|
|
18
|
+
|
|
19
|
+
| Tool | Description |
|
|
20
|
+
|------|-------------|
|
|
21
|
+
| `mail_get_accounts` | List all accounts and their mailboxes |
|
|
22
|
+
| `mail_count_unread` | Count unread messages per account/mailbox |
|
|
23
|
+
| `mail_list_messages` | List messages with filters (account, mailbox, date range, unread) |
|
|
24
|
+
| `mail_read_message` | Read full content of a message by ID |
|
|
25
|
+
| `mail_search` | Search by subject, sender, or both |
|
|
26
|
+
| `mail_mark_read` | Mark a message as read |
|
|
27
|
+
|
|
28
|
+
## Examples
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
"List my mail accounts" → mail_get_accounts
|
|
32
|
+
"Show unread count" → mail_count_unread
|
|
33
|
+
"Yesterday's emails" → mail_list_messages { date_from: "2026-03-26" }
|
|
34
|
+
"Search for GitHub emails" → mail_search { query: "GitHub" }
|
|
35
|
+
"Read message 12345" → mail_read_message { message_id: 12345 }
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Limitations
|
|
39
|
+
|
|
40
|
+
- macOS only (uses AppleScript via `osascript`)
|
|
41
|
+
- Subject search is case-sensitive (AppleScript limitation)
|
|
42
|
+
- Sender/body search fetches recent messages and filters in JS (last 30 days, max 500)
|
|
43
|
+
- Mail.app must be running
|
|
44
|
+
|
|
45
|
+
## License
|
|
46
|
+
|
|
47
|
+
MIT
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
const TIMEOUT_MS = 15_000;
|
|
3
|
+
export class AppleMailError extends Error {
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "AppleMailError";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export async function runAppleScript(script) {
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
execFile("osascript", ["-e", script], { timeout: TIMEOUT_MS }, (err, stdout, stderr) => {
|
|
12
|
+
if (err) {
|
|
13
|
+
const msg = stderr || err.message;
|
|
14
|
+
if (msg.includes("not running") || msg.includes("-600"))
|
|
15
|
+
return reject(new AppleMailError("Apple Mail is not running. Please open Mail.app and try again."));
|
|
16
|
+
if (msg.includes("not allowed") || msg.includes("not permitted"))
|
|
17
|
+
return reject(new AppleMailError("Permission denied. Grant automation access in System Settings > Privacy & Security > Automation."));
|
|
18
|
+
if (msg.includes("timed out") || err.code === "ETIMEDOUT")
|
|
19
|
+
return reject(new AppleMailError("AppleScript execution timed out."));
|
|
20
|
+
return reject(new AppleMailError(msg.trim()));
|
|
21
|
+
}
|
|
22
|
+
resolve(stdout.trimEnd());
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
export function escapeForAppleScript(str) {
|
|
27
|
+
return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=applescript.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"applescript.js","sourceRoot":"","sources":["../src/applescript.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C,MAAM,UAAU,GAAG,MAAM,CAAC;AAE1B,MAAM,OAAO,cAAe,SAAQ,KAAK;IACvC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,MAAc;IACjD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,QAAQ,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YACrF,IAAI,GAAG,EAAE,CAAC;gBACR,MAAM,GAAG,GAAG,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC;gBAClC,IAAI,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC;oBACrD,OAAO,MAAM,CAAC,IAAI,cAAc,CAAC,gEAAgE,CAAC,CAAC,CAAC;gBACtG,IAAI,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,eAAe,CAAC;oBAC9D,OAAO,MAAM,CAAC,IAAI,cAAc,CAAC,kGAAkG,CAAC,CAAC,CAAC;gBACxI,IAAI,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAK,GAA6B,CAAC,IAAI,KAAK,WAAW;oBAClF,OAAO,MAAM,CAAC,IAAI,cAAc,CAAC,kCAAkC,CAAC,CAAC,CAAC;gBACxE,OAAO,MAAM,CAAC,IAAI,cAAc,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;YAChD,CAAC;YACD,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,GAAW;IAC9C,OAAO,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AACzD,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare function success(data: unknown): {
|
|
2
|
+
content: {
|
|
3
|
+
type: "text";
|
|
4
|
+
text: string;
|
|
5
|
+
}[];
|
|
6
|
+
};
|
|
7
|
+
export declare function error(message: string): {
|
|
8
|
+
content: {
|
|
9
|
+
type: "text";
|
|
10
|
+
text: string;
|
|
11
|
+
}[];
|
|
12
|
+
isError: true;
|
|
13
|
+
};
|
|
14
|
+
export declare function withErrorHandling<T extends Record<string, unknown>>(fn: (args: T) => Promise<ReturnType<typeof success | typeof error>>): (args: T) => Promise<{
|
|
15
|
+
content: {
|
|
16
|
+
type: "text";
|
|
17
|
+
text: string;
|
|
18
|
+
}[];
|
|
19
|
+
}>;
|
package/dist/helpers.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { AppleMailError } from "./applescript.js";
|
|
2
|
+
export function success(data) {
|
|
3
|
+
return {
|
|
4
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
export function error(message) {
|
|
8
|
+
return {
|
|
9
|
+
content: [{ type: "text", text: JSON.stringify({ error: message }) }],
|
|
10
|
+
isError: true,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function withErrorHandling(fn) {
|
|
14
|
+
return async (args) => {
|
|
15
|
+
try {
|
|
16
|
+
return await fn(args);
|
|
17
|
+
}
|
|
18
|
+
catch (e) {
|
|
19
|
+
if (e instanceof AppleMailError)
|
|
20
|
+
return error(e.message);
|
|
21
|
+
throw e;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.js","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,MAAM,UAAU,OAAO,CAAC,IAAa;IACnC,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;KAC1E,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,OAAe;IACnC,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;QAC9E,OAAO,EAAE,IAAa;KACvB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,EAAmE;IAEnE,OAAO,KAAK,EAAE,IAAO,EAAE,EAAE;QACvB,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,CAAC,IAAI,CAAC,CAAC;QACxB,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAI,CAAC,YAAY,cAAc;gBAAE,OAAO,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;YACzD,MAAM,CAAC,CAAC;QACV,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { createServer } from "./server.js";
|
|
4
|
+
if (process.platform !== "darwin") {
|
|
5
|
+
console.error("apple-mail-mcp requires macOS with Apple Mail.");
|
|
6
|
+
process.exit(1);
|
|
7
|
+
}
|
|
8
|
+
const server = createServer();
|
|
9
|
+
const transport = new StdioServerTransport();
|
|
10
|
+
await server.connect(transport);
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;IAClC,OAAO,CAAC,KAAK,CAAC,gDAAgD,CAAC,CAAC;IAChE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;AAC9B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;AAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC"}
|
package/dist/server.d.ts
ADDED
package/dist/server.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { registerAccountTools } from "./tools/accounts.js";
|
|
3
|
+
import { registerMessageTools } from "./tools/messages.js";
|
|
4
|
+
import { registerSearchTools } from "./tools/search.js";
|
|
5
|
+
export function createServer() {
|
|
6
|
+
const server = new McpServer({
|
|
7
|
+
name: "apple-mail",
|
|
8
|
+
version: "0.1.0",
|
|
9
|
+
});
|
|
10
|
+
registerAccountTools(server);
|
|
11
|
+
registerMessageTools(server);
|
|
12
|
+
registerSearchTools(server);
|
|
13
|
+
return server;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,MAAM,UAAU,YAAY;IAC1B,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,YAAY;QAClB,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC7B,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC7B,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAE5B,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runAppleScript } from "../applescript.js";
|
|
3
|
+
import { success, withErrorHandling } from "../helpers.js";
|
|
4
|
+
export function registerAccountTools(server) {
|
|
5
|
+
server.tool("mail_get_accounts", "List all mail accounts and their mailboxes", {}, withErrorHandling(async () => {
|
|
6
|
+
const accountNames = await runAppleScript(`tell application "Mail" to get name of every account`);
|
|
7
|
+
if (!accountNames)
|
|
8
|
+
return success([]);
|
|
9
|
+
const names = accountNames.split(", ");
|
|
10
|
+
const accounts = [];
|
|
11
|
+
for (const name of names) {
|
|
12
|
+
const mailboxes = await runAppleScript(`tell application "Mail" to get name of every mailbox of account "${name}"`);
|
|
13
|
+
accounts.push({
|
|
14
|
+
name,
|
|
15
|
+
mailboxes: mailboxes ? mailboxes.split(", ") : [],
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return success(accounts);
|
|
19
|
+
}));
|
|
20
|
+
server.tool("mail_count_unread", "Count unread messages in a mailbox", {
|
|
21
|
+
account: z.string().optional().describe("Account name (e.g. 'iCloud', 'Gmail'). Omit for all accounts."),
|
|
22
|
+
mailbox: z.string().optional().describe("Mailbox name (e.g. 'INBOX'). Omit for inbox."),
|
|
23
|
+
}, withErrorHandling(async ({ account, mailbox }) => {
|
|
24
|
+
if (account) {
|
|
25
|
+
const target = mailbox
|
|
26
|
+
? `mailbox "${mailbox}" of account "${account}"`
|
|
27
|
+
: `inbox`;
|
|
28
|
+
const count = await runAppleScript(`tell application "Mail" to get unread count of ${target}`);
|
|
29
|
+
return success({ account, mailbox: mailbox || "INBOX", unread: parseInt(count, 10) });
|
|
30
|
+
}
|
|
31
|
+
// All accounts
|
|
32
|
+
const accountNames = await runAppleScript(`tell application "Mail" to get name of every account`);
|
|
33
|
+
if (!accountNames)
|
|
34
|
+
return success([]);
|
|
35
|
+
const results = [];
|
|
36
|
+
for (const name of accountNames.split(", ")) {
|
|
37
|
+
const script = `
|
|
38
|
+
tell application "Mail"
|
|
39
|
+
set acct to account "${name}"
|
|
40
|
+
set output to ""
|
|
41
|
+
repeat with mbox in every mailbox of acct
|
|
42
|
+
set cnt to unread count of mbox
|
|
43
|
+
if cnt > 0 then
|
|
44
|
+
set output to output & (name of mbox) & "\\t" & cnt & "\\n"
|
|
45
|
+
end if
|
|
46
|
+
end repeat
|
|
47
|
+
return output
|
|
48
|
+
end tell`;
|
|
49
|
+
const raw = await runAppleScript(script);
|
|
50
|
+
if (!raw)
|
|
51
|
+
continue;
|
|
52
|
+
for (const line of raw.split("\n")) {
|
|
53
|
+
if (!line)
|
|
54
|
+
continue;
|
|
55
|
+
const [mboxName, cnt] = line.split("\t");
|
|
56
|
+
results.push({ account: name, mailbox: mboxName, unread: parseInt(cnt, 10) });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return success(results);
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=accounts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accounts.js","sourceRoot":"","sources":["../../src/tools/accounts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runAppleScript, escapeForAppleScript } from "../applescript.js";
|
|
3
|
+
import { success, error, withErrorHandling } from "../helpers.js";
|
|
4
|
+
function buildMailboxTarget(account, mailbox) {
|
|
5
|
+
if (account && mailbox)
|
|
6
|
+
return `mailbox "${mailbox}" of account "${account}"`;
|
|
7
|
+
if (account)
|
|
8
|
+
return `inbox of account "${account}"`;
|
|
9
|
+
return "inbox";
|
|
10
|
+
}
|
|
11
|
+
function formatDate(isoDate) {
|
|
12
|
+
const d = new Date(isoDate);
|
|
13
|
+
return d.toLocaleDateString("en-US", {
|
|
14
|
+
weekday: "long", year: "numeric", month: "long", day: "numeric",
|
|
15
|
+
hour: "numeric", minute: "numeric", second: "numeric",
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
function parseMessages(raw) {
|
|
19
|
+
if (!raw)
|
|
20
|
+
return [];
|
|
21
|
+
const messages = [];
|
|
22
|
+
for (const line of raw.split("\n")) {
|
|
23
|
+
if (!line)
|
|
24
|
+
continue;
|
|
25
|
+
const parts = line.split("\t");
|
|
26
|
+
if (parts.length < 5)
|
|
27
|
+
continue;
|
|
28
|
+
messages.push({
|
|
29
|
+
id: parseInt(parts[0], 10),
|
|
30
|
+
subject: parts[1],
|
|
31
|
+
sender: parts[2],
|
|
32
|
+
date: parts[3],
|
|
33
|
+
read: parts[4] === "true",
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return messages;
|
|
37
|
+
}
|
|
38
|
+
export function registerMessageTools(server) {
|
|
39
|
+
server.tool("mail_list_messages", "List messages in a mailbox with optional filters", {
|
|
40
|
+
account: z.string().optional().describe("Account name"),
|
|
41
|
+
mailbox: z.string().optional().describe("Mailbox name (default: INBOX)"),
|
|
42
|
+
limit: z.number().int().positive().max(100).default(20).describe("Max messages to return"),
|
|
43
|
+
unread_only: z.boolean().default(false).describe("Only show unread messages"),
|
|
44
|
+
date_from: z.string().optional().describe("ISO 8601 date, e.g. '2026-03-26'"),
|
|
45
|
+
date_to: z.string().optional().describe("ISO 8601 date, e.g. '2026-03-27'"),
|
|
46
|
+
}, withErrorHandling(async ({ account, mailbox, limit, unread_only, date_from, date_to }) => {
|
|
47
|
+
const target = buildMailboxTarget(account, mailbox);
|
|
48
|
+
const conditions = [];
|
|
49
|
+
if (unread_only)
|
|
50
|
+
conditions.push("read status is false");
|
|
51
|
+
if (date_from)
|
|
52
|
+
conditions.push(`date received >= date "${formatDate(date_from)}"`);
|
|
53
|
+
if (date_to)
|
|
54
|
+
conditions.push(`date received <= date "${formatDate(date_to)}"`);
|
|
55
|
+
const whereClause = conditions.length > 0
|
|
56
|
+
? ` whose ${conditions.join(" and ")}`
|
|
57
|
+
: "";
|
|
58
|
+
const script = `
|
|
59
|
+
tell application "Mail"
|
|
60
|
+
set msgs to (every message of ${target}${whereClause})
|
|
61
|
+
set maxCount to ${limit}
|
|
62
|
+
if (count of msgs) < maxCount then set maxCount to (count of msgs)
|
|
63
|
+
set output to ""
|
|
64
|
+
repeat with i from 1 to maxCount
|
|
65
|
+
set m to item i of msgs
|
|
66
|
+
set output to output & (id of m) & "\\t" & (subject of m) & "\\t" & (sender of m) & "\\t" & (date received of m as string) & "\\t" & (read status of m) & "\\n"
|
|
67
|
+
end repeat
|
|
68
|
+
return output
|
|
69
|
+
end tell`;
|
|
70
|
+
const raw = await runAppleScript(script);
|
|
71
|
+
return success(parseMessages(raw));
|
|
72
|
+
}));
|
|
73
|
+
server.tool("mail_read_message", "Read the full content of a specific message", {
|
|
74
|
+
message_id: z.number().int().describe("Message ID"),
|
|
75
|
+
account: z.string().optional().describe("Account name (speeds up lookup)"),
|
|
76
|
+
mailbox: z.string().optional().describe("Mailbox name (speeds up lookup)"),
|
|
77
|
+
}, withErrorHandling(async ({ message_id, account, mailbox }) => {
|
|
78
|
+
// If account and mailbox provided, direct lookup
|
|
79
|
+
if (account) {
|
|
80
|
+
const target = mailbox
|
|
81
|
+
? `mailbox "${escapeForAppleScript(mailbox)}" of account "${escapeForAppleScript(account)}"`
|
|
82
|
+
: `inbox of account "${escapeForAppleScript(account)}"`;
|
|
83
|
+
const script = `
|
|
84
|
+
tell application "Mail"
|
|
85
|
+
set m to message id ${message_id} of ${target}
|
|
86
|
+
set subj to subject of m
|
|
87
|
+
set sndr to sender of m
|
|
88
|
+
set rcvd to date received of m as string
|
|
89
|
+
set rd to read status of m
|
|
90
|
+
set cnt to content of m
|
|
91
|
+
set toList to address of every to recipient of m
|
|
92
|
+
set ccList to address of every cc recipient of m
|
|
93
|
+
return subj & "\\n===FIELD===\\n" & sndr & "\\n===FIELD===\\n" & rcvd & "\\n===FIELD===\\n" & rd & "\\n===FIELD===\\n" & cnt & "\\n===FIELD===\\n" & toList & "\\n===FIELD===\\n" & ccList
|
|
94
|
+
end tell`;
|
|
95
|
+
const raw = await runAppleScript(script);
|
|
96
|
+
const fields = raw.split("\n===FIELD===\n");
|
|
97
|
+
return success({
|
|
98
|
+
id: message_id,
|
|
99
|
+
subject: fields[0] || "",
|
|
100
|
+
sender: fields[1] || "",
|
|
101
|
+
date: fields[2] || "",
|
|
102
|
+
read: fields[3] === "true",
|
|
103
|
+
content: fields[4] || "",
|
|
104
|
+
to: fields[5] || "",
|
|
105
|
+
cc: fields[6] || "",
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
// Scan all accounts' inboxes
|
|
109
|
+
const accountNames = await runAppleScript(`tell application "Mail" to get name of every account`);
|
|
110
|
+
if (!accountNames)
|
|
111
|
+
return error(`Message ${message_id} not found.`);
|
|
112
|
+
for (const acctName of accountNames.split(", ")) {
|
|
113
|
+
try {
|
|
114
|
+
const script = `
|
|
115
|
+
tell application "Mail"
|
|
116
|
+
set m to message id ${message_id} of inbox of account "${acctName}"
|
|
117
|
+
set subj to subject of m
|
|
118
|
+
set sndr to sender of m
|
|
119
|
+
set rcvd to date received of m as string
|
|
120
|
+
set rd to read status of m
|
|
121
|
+
set cnt to content of m
|
|
122
|
+
set toList to address of every to recipient of m
|
|
123
|
+
set ccList to address of every cc recipient of m
|
|
124
|
+
return subj & "\\n===FIELD===\\n" & sndr & "\\n===FIELD===\\n" & rcvd & "\\n===FIELD===\\n" & rd & "\\n===FIELD===\\n" & cnt & "\\n===FIELD===\\n" & toList & "\\n===FIELD===\\n" & ccList
|
|
125
|
+
end tell`;
|
|
126
|
+
const raw = await runAppleScript(script);
|
|
127
|
+
const fields = raw.split("\n===FIELD===\n");
|
|
128
|
+
return success({
|
|
129
|
+
id: message_id,
|
|
130
|
+
account: acctName,
|
|
131
|
+
subject: fields[0] || "",
|
|
132
|
+
sender: fields[1] || "",
|
|
133
|
+
date: fields[2] || "",
|
|
134
|
+
read: fields[3] === "true",
|
|
135
|
+
content: fields[4] || "",
|
|
136
|
+
to: fields[5] || "",
|
|
137
|
+
cc: fields[6] || "",
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
continue; // Not in this account, try next
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return error(`Message ${message_id} not found in any inbox.`);
|
|
145
|
+
}));
|
|
146
|
+
server.tool("mail_mark_read", "Mark a message as read", {
|
|
147
|
+
message_id: z.number().int().describe("Message ID"),
|
|
148
|
+
account: z.string().optional().describe("Account name"),
|
|
149
|
+
mailbox: z.string().optional().describe("Mailbox name"),
|
|
150
|
+
}, withErrorHandling(async ({ message_id, account, mailbox }) => {
|
|
151
|
+
const target = account
|
|
152
|
+
? (mailbox
|
|
153
|
+
? `mailbox "${escapeForAppleScript(mailbox)}" of account "${escapeForAppleScript(account)}"`
|
|
154
|
+
: `inbox of account "${escapeForAppleScript(account)}"`)
|
|
155
|
+
: "inbox";
|
|
156
|
+
await runAppleScript(`tell application "Mail" to set read status of message id ${message_id} of ${target} to true`);
|
|
157
|
+
return success({ message_id, marked_read: true });
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=messages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../../src/tools/messages.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runAppleScript, escapeForAppleScript } from "../applescript.js";
|
|
3
|
+
import { success, withErrorHandling } from "../helpers.js";
|
|
4
|
+
function parseMessages(raw) {
|
|
5
|
+
if (!raw)
|
|
6
|
+
return [];
|
|
7
|
+
const messages = [];
|
|
8
|
+
for (const line of raw.split("\n")) {
|
|
9
|
+
if (!line)
|
|
10
|
+
continue;
|
|
11
|
+
const parts = line.split("\t");
|
|
12
|
+
if (parts.length < 5)
|
|
13
|
+
continue;
|
|
14
|
+
messages.push({
|
|
15
|
+
id: parseInt(parts[0], 10),
|
|
16
|
+
subject: parts[1],
|
|
17
|
+
sender: parts[2],
|
|
18
|
+
date: parts[3],
|
|
19
|
+
read: parts[4] === "true",
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
return messages;
|
|
23
|
+
}
|
|
24
|
+
export function registerSearchTools(server) {
|
|
25
|
+
server.tool("mail_search", "Search messages by subject or sender", {
|
|
26
|
+
query: z.string().min(1).describe("Search keyword"),
|
|
27
|
+
field: z.enum(["subject", "sender", "all"]).default("all").describe("Field to search"),
|
|
28
|
+
account: z.string().optional().describe("Account name"),
|
|
29
|
+
mailbox: z.string().optional().describe("Mailbox name (default: INBOX)"),
|
|
30
|
+
limit: z.number().int().positive().max(50).default(10).describe("Max results"),
|
|
31
|
+
}, withErrorHandling(async ({ query, field, account, mailbox, limit }) => {
|
|
32
|
+
const escaped = escapeForAppleScript(query);
|
|
33
|
+
const target = account
|
|
34
|
+
? (mailbox
|
|
35
|
+
? `mailbox "${escapeForAppleScript(mailbox)}" of account "${escapeForAppleScript(account)}"`
|
|
36
|
+
: `inbox of account "${escapeForAppleScript(account)}"`)
|
|
37
|
+
: "inbox";
|
|
38
|
+
// Subject search: use native AppleScript filter
|
|
39
|
+
if (field === "subject") {
|
|
40
|
+
const script = `
|
|
41
|
+
tell application "Mail"
|
|
42
|
+
set msgs to (every message of ${target} whose subject contains "${escaped}")
|
|
43
|
+
set maxCount to ${limit}
|
|
44
|
+
if (count of msgs) < maxCount then set maxCount to (count of msgs)
|
|
45
|
+
set output to ""
|
|
46
|
+
repeat with i from 1 to maxCount
|
|
47
|
+
set m to item i of msgs
|
|
48
|
+
set output to output & (id of m) & "\\t" & (subject of m) & "\\t" & (sender of m) & "\\t" & (date received of m as string) & "\\t" & (read status of m) & "\\n"
|
|
49
|
+
end repeat
|
|
50
|
+
return output
|
|
51
|
+
end tell`;
|
|
52
|
+
return success(parseMessages(await runAppleScript(script)));
|
|
53
|
+
}
|
|
54
|
+
// Sender or all: fetch recent messages, filter in JS
|
|
55
|
+
const fetchLimit = 500;
|
|
56
|
+
const script = `
|
|
57
|
+
tell application "Mail"
|
|
58
|
+
set thirtyDaysAgo to (current date) - 30 * days
|
|
59
|
+
set msgs to (every message of ${target} whose date received >= thirtyDaysAgo)
|
|
60
|
+
set maxCount to ${fetchLimit}
|
|
61
|
+
if (count of msgs) < maxCount then set maxCount to (count of msgs)
|
|
62
|
+
set output to ""
|
|
63
|
+
repeat with i from 1 to maxCount
|
|
64
|
+
set m to item i of msgs
|
|
65
|
+
set output to output & (id of m) & "\\t" & (subject of m) & "\\t" & (sender of m) & "\\t" & (date received of m as string) & "\\t" & (read status of m) & "\\n"
|
|
66
|
+
end repeat
|
|
67
|
+
return output
|
|
68
|
+
end tell`;
|
|
69
|
+
const all = parseMessages(await runAppleScript(script));
|
|
70
|
+
const q = query.toLowerCase();
|
|
71
|
+
const filtered = all.filter((m) => {
|
|
72
|
+
if (field === "sender")
|
|
73
|
+
return m.sender.toLowerCase().includes(q);
|
|
74
|
+
// "all" — match subject or sender
|
|
75
|
+
return m.subject.toLowerCase().includes(q) || m.sender.toLowerCase().includes(q);
|
|
76
|
+
});
|
|
77
|
+
return success(filtered.slice(0, limit));
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/tools/search.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zyx1121/apple-mail-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for Apple Mail — read, search, and manage emails via Claude Code",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"apple-mail-mcp": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"prepublishOnly": "npm run build"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"mcp",
|
|
20
|
+
"apple-mail",
|
|
21
|
+
"claude",
|
|
22
|
+
"email",
|
|
23
|
+
"macos"
|
|
24
|
+
],
|
|
25
|
+
"author": "zyx1121",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=18"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
32
|
+
"zod": "^3.25.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^20.0.0",
|
|
36
|
+
"typescript": "^5.0.0"
|
|
37
|
+
}
|
|
38
|
+
}
|