@sempervirens-labs/apple-mail-mcp 1.0.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.
@@ -0,0 +1,13 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "WebFetch(domain:github.com)",
5
+ "mcp__trigger__search_docs",
6
+ "WebFetch(domain:raw.githubusercontent.com)",
7
+ "WebFetch(domain:www.npmjs.com)",
8
+ "Bash(bun install)",
9
+ "Bash(osascript:*)",
10
+ "Bash(npm publish:*)"
11
+ ]
12
+ }
13
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025
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,261 @@
1
+ # Apple Mail MCP
2
+
3
+ An MCP (Model Context Protocol) server for Apple Mail on macOS. Allows AI assistants like Claude to read, search, and send emails through Apple Mail.
4
+
5
+ ## Features
6
+
7
+ - **List Accounts** - Get all configured email accounts
8
+ - **List Mailboxes** - Get all mailboxes/folders for any account
9
+ - **Get Emails** - Retrieve recent emails from any mailbox
10
+ - **Search Emails** - Search by subject, sender, or content
11
+ - **Unread Count** - Get unread email counts
12
+ - **Send Email** - Compose and send emails with CC/BCC support
13
+
14
+ ## Requirements
15
+
16
+ - macOS (uses AppleScript to communicate with Apple Mail)
17
+ - [Bun](https://bun.sh/) runtime
18
+ - Apple Mail app configured with at least one email account
19
+
20
+ ## Installation
21
+
22
+ ### From Source
23
+
24
+ ```bash
25
+ git clone https://github.com/yourusername/apple-mail-mcp.git
26
+ cd apple-mail-mcp
27
+ bun install
28
+ ```
29
+
30
+ ### Using bunx (no installation required)
31
+
32
+ ```bash
33
+ bunx @sempervirens-labs/apple-mail-mcp
34
+ ```
35
+
36
+ ## Configuration
37
+
38
+ ### Claude Code
39
+
40
+ Add to your `~/.claude/settings.json`:
41
+
42
+ ```json
43
+ {
44
+ "mcpServers": {
45
+ "apple-mail": {
46
+ "command": "bun",
47
+ "args": ["run", "/path/to/apple-mail-mcp/src/index.ts"]
48
+ }
49
+ }
50
+ }
51
+ ```
52
+
53
+ Or if published to npm:
54
+
55
+ ```json
56
+ {
57
+ "mcpServers": {
58
+ "apple-mail": {
59
+ "command": "bunx",
60
+ "args": ["apple-mail-mcp"]
61
+ }
62
+ }
63
+ }
64
+ ```
65
+
66
+ After adding the configuration, restart Claude Code for the changes to take effect.
67
+
68
+ ### Cursor
69
+
70
+ Add to your Cursor MCP settings (Settings > MCP Servers):
71
+
72
+ ```json
73
+ {
74
+ "mcpServers": {
75
+ "apple-mail": {
76
+ "command": "bun",
77
+ "args": ["run", "/path/to/apple-mail-mcp/src/index.ts"]
78
+ }
79
+ }
80
+ }
81
+ ```
82
+
83
+ Or via Cursor's settings UI:
84
+ 1. Open Cursor Settings (`Cmd + ,`)
85
+ 2. Search for "MCP" or navigate to Extensions > MCP Servers
86
+ 3. Click "Add Server"
87
+ 4. Enter:
88
+ - **Name**: `apple-mail`
89
+ - **Command**: `bun`
90
+ - **Arguments**: `run`, `/path/to/apple-mail-mcp/src/index.ts`
91
+
92
+ ### Claude Desktop
93
+
94
+ Add to your `~/Library/Application Support/Claude/claude_desktop_config.json`:
95
+
96
+ ```json
97
+ {
98
+ "mcpServers": {
99
+ "apple-mail": {
100
+ "command": "bun",
101
+ "args": ["run", "/path/to/apple-mail-mcp/src/index.ts"]
102
+ }
103
+ }
104
+ }
105
+ ```
106
+
107
+ ## Available Tools
108
+
109
+ ### `mail_list_accounts`
110
+
111
+ List all email accounts configured in Apple Mail.
112
+
113
+ ```
114
+ No parameters required
115
+ ```
116
+
117
+ **Example response:**
118
+ ```json
119
+ {
120
+ "accounts": ["personal@gmail.com", "iCloud", "work@company.com"]
121
+ }
122
+ ```
123
+
124
+ ### `mail_list_mailboxes`
125
+
126
+ List all mailboxes for a specific account or all accounts.
127
+
128
+ | Parameter | Type | Required | Description |
129
+ |-----------|------|----------|-------------|
130
+ | `account` | string | No | Account name to list mailboxes for |
131
+
132
+ **Example response:**
133
+ ```json
134
+ {
135
+ "mailboxes": [
136
+ {
137
+ "account": "iCloud",
138
+ "mailboxes": ["INBOX", "Drafts", "Sent Messages", "Archive", "Junk"]
139
+ }
140
+ ]
141
+ }
142
+ ```
143
+
144
+ ### `mail_get_emails`
145
+
146
+ Get recent emails from a mailbox.
147
+
148
+ | Parameter | Type | Required | Default | Description |
149
+ |-----------|------|----------|---------|-------------|
150
+ | `account` | string | No | - | Account name |
151
+ | `mailbox` | string | No | "INBOX" | Mailbox name |
152
+ | `limit` | number | No | 10 | Max emails to retrieve |
153
+ | `includeContent` | boolean | No | false | Include email body |
154
+
155
+ **Example response:**
156
+ ```json
157
+ {
158
+ "emails": [
159
+ {
160
+ "id": 12345,
161
+ "subject": "Meeting tomorrow",
162
+ "sender": "John Doe <john@example.com>",
163
+ "dateSent": "Monday, 10. January 2025 at 09:30:00",
164
+ "isRead": false
165
+ }
166
+ ],
167
+ "count": 1
168
+ }
169
+ ```
170
+
171
+ ### `mail_search`
172
+
173
+ Search emails by subject, sender, or content.
174
+
175
+ | Parameter | Type | Required | Default | Description |
176
+ |-----------|------|----------|---------|-------------|
177
+ | `query` | string | **Yes** | - | Search query |
178
+ | `account` | string | No | - | Limit search to account |
179
+ | `mailbox` | string | No | - | Limit search to mailbox |
180
+ | `limit` | number | No | 10 | Max results |
181
+
182
+ ### `mail_get_unread_count`
183
+
184
+ Get the count of unread emails.
185
+
186
+ | Parameter | Type | Required | Description |
187
+ |-----------|------|----------|-------------|
188
+ | `account` | string | No | Account name |
189
+ | `mailbox` | string | No | Mailbox name |
190
+
191
+ **Example response:**
192
+ ```json
193
+ {
194
+ "unreadCount": 42
195
+ }
196
+ ```
197
+
198
+ ### `mail_send`
199
+
200
+ Send an email using Apple Mail.
201
+
202
+ | Parameter | Type | Required | Description |
203
+ |-----------|------|----------|-------------|
204
+ | `to` | string or string[] | **Yes** | Recipient email(s) |
205
+ | `subject` | string | **Yes** | Email subject |
206
+ | `body` | string | **Yes** | Email body |
207
+ | `cc` | string or string[] | No | CC recipient(s) |
208
+ | `bcc` | string or string[] | No | BCC recipient(s) |
209
+ | `from` | string | No | Sender (must be configured account) |
210
+
211
+ **Example response:**
212
+ ```json
213
+ {
214
+ "success": true,
215
+ "message": "Message sent successfully"
216
+ }
217
+ ```
218
+
219
+ ## Permissions
220
+
221
+ On first use, macOS will prompt you to grant permissions:
222
+ 1. **Automation** - Allow the terminal/app to control Apple Mail
223
+ 2. **Mail Access** - Allow access to your email data
224
+
225
+ You can manage these in System Settings > Privacy & Security > Automation.
226
+
227
+ ## Development
228
+
229
+ ```bash
230
+ # Install dependencies
231
+ bun install
232
+
233
+ # Run in development mode
234
+ bun run dev
235
+
236
+ # Run the server
237
+ bun run start
238
+ ```
239
+
240
+ ## Troubleshooting
241
+
242
+ ### "No accounts found"
243
+ - Ensure Apple Mail is running and has at least one account configured
244
+ - Check that automation permissions are granted in System Settings
245
+
246
+ ### "Operation not permitted"
247
+ - Grant automation permissions: System Settings > Privacy & Security > Automation
248
+ - Ensure the terminal/app running the MCP server has permission to control Mail
249
+
250
+ ### Server not connecting
251
+ - Restart your AI client (Claude Code, Cursor, etc.) after adding the MCP configuration
252
+ - Verify the path to the server is correct in your configuration
253
+ - Check that Bun is installed and accessible from your PATH
254
+
255
+ ## License
256
+
257
+ MIT
258
+
259
+ ## Credits
260
+
261
+ Built as an alternative to [apple-mcp](https://github.com/Dhravya/apple-mcp) with working mail operations using direct AppleScript execution.
package/bun.lock ADDED
@@ -0,0 +1,203 @@
1
+ {
2
+ "lockfileVersion": 1,
3
+ "configVersion": 1,
4
+ "workspaces": {
5
+ "": {
6
+ "name": "apple-mail-mcp",
7
+ "dependencies": {
8
+ "@modelcontextprotocol/sdk": "^1.0.0",
9
+ },
10
+ "devDependencies": {
11
+ "@types/node": "^20.0.0",
12
+ "typescript": "^5.0.0",
13
+ },
14
+ },
15
+ },
16
+ "packages": {
17
+ "@hono/node-server": ["@hono/node-server@1.19.8", "", { "peerDependencies": { "hono": "^4" } }, "sha512-0/g2lIOPzX8f3vzW1ggQgvG5mjtFBDBHFAzI5SFAi2DzSqS9luJwqg9T6O/gKYLi+inS7eNxBeIFkkghIPvrMA=="],
18
+
19
+ "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.25.2", "", { "dependencies": { "@hono/node-server": "^1.19.7", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "jose": "^6.1.1", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.0" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-LZFeo4F9M5qOhC/Uc1aQSrBHxMrvxett+9KLHt7OhcExtoiRN9DKgbZffMP/nxjutWDQpfMDfP3nkHI4X9ijww=="],
20
+
21
+ "@types/node": ["@types/node@20.19.28", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-VyKBr25BuFDzBFCK5sUM6ZXiWfqgCTwTAOK8qzGV/m9FCirXYDlmczJ+d5dXBAQALGCdRRdbteKYfJ84NGEusw=="],
22
+
23
+ "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
24
+
25
+ "ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
26
+
27
+ "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
28
+
29
+ "body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
30
+
31
+ "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
32
+
33
+ "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
34
+
35
+ "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
36
+
37
+ "content-disposition": ["content-disposition@1.0.1", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="],
38
+
39
+ "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
40
+
41
+ "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
42
+
43
+ "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
44
+
45
+ "cors": ["cors@2.8.5", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g=="],
46
+
47
+ "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
48
+
49
+ "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
50
+
51
+ "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
52
+
53
+ "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
54
+
55
+ "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
56
+
57
+ "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
58
+
59
+ "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
60
+
61
+ "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
62
+
63
+ "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
64
+
65
+ "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
66
+
67
+ "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
68
+
69
+ "eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="],
70
+
71
+ "eventsource-parser": ["eventsource-parser@3.0.6", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="],
72
+
73
+ "express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
74
+
75
+ "express-rate-limit": ["express-rate-limit@7.5.1", "", { "peerDependencies": { "express": ">= 4.11" } }, "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw=="],
76
+
77
+ "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
78
+
79
+ "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="],
80
+
81
+ "finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="],
82
+
83
+ "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
84
+
85
+ "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
86
+
87
+ "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
88
+
89
+ "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
90
+
91
+ "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
92
+
93
+ "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
94
+
95
+ "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
96
+
97
+ "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
98
+
99
+ "hono": ["hono@4.11.3", "", {}, "sha512-PmQi306+M/ct/m5s66Hrg+adPnkD5jiO6IjA7WhWw0gSBSo1EcRegwuI1deZ+wd5pzCGynCcn2DprnE4/yEV4w=="],
100
+
101
+ "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
102
+
103
+ "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
104
+
105
+ "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
106
+
107
+ "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
108
+
109
+ "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="],
110
+
111
+ "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
112
+
113
+ "jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="],
114
+
115
+ "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
116
+
117
+ "json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="],
118
+
119
+ "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
120
+
121
+ "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
122
+
123
+ "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
124
+
125
+ "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
126
+
127
+ "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
128
+
129
+ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
130
+
131
+ "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
132
+
133
+ "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
134
+
135
+ "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
136
+
137
+ "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
138
+
139
+ "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
140
+
141
+ "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
142
+
143
+ "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
144
+
145
+ "path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="],
146
+
147
+ "pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="],
148
+
149
+ "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
150
+
151
+ "qs": ["qs@6.14.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ=="],
152
+
153
+ "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
154
+
155
+ "raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
156
+
157
+ "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
158
+
159
+ "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
160
+
161
+ "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
162
+
163
+ "send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="],
164
+
165
+ "serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="],
166
+
167
+ "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
168
+
169
+ "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
170
+
171
+ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
172
+
173
+ "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],
174
+
175
+ "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="],
176
+
177
+ "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
178
+
179
+ "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
180
+
181
+ "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
182
+
183
+ "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
184
+
185
+ "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
186
+
187
+ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
188
+
189
+ "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
190
+
191
+ "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
192
+
193
+ "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
194
+
195
+ "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
196
+
197
+ "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
198
+
199
+ "zod": ["zod@4.3.5", "", {}, "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g=="],
200
+
201
+ "zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="],
202
+ }
203
+ }
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@sempervirens-labs/apple-mail-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for Apple Mail - list accounts, mailboxes, search emails, and send messages",
5
+ "type": "module",
6
+ "main": "src/index.ts",
7
+ "bin": {
8
+ "apple-mail-mcp": "./src/index.ts"
9
+ },
10
+ "scripts": {
11
+ "start": "bun run src/index.ts",
12
+ "dev": "bun run --watch src/index.ts"
13
+ },
14
+ "keywords": [
15
+ "mcp",
16
+ "apple",
17
+ "mail",
18
+ "email",
19
+ "macos",
20
+ "applescript",
21
+ "claude"
22
+ ],
23
+ "author": "Robert Bouschery",
24
+ "license": "MIT",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/rbouschery/apple-mail-mcp.git"
28
+ },
29
+ "homepage": "https://github.com/rbouschery/apple-mail-mcp#readme",
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "dependencies": {
34
+ "@modelcontextprotocol/sdk": "^1.0.0"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^20.0.0",
38
+ "typescript": "^5.0.0"
39
+ },
40
+ "engines": {
41
+ "node": ">=18"
42
+ }
43
+ }
@@ -0,0 +1,356 @@
1
+ import { execSync } from "child_process";
2
+
3
+ /**
4
+ * Execute an AppleScript and return the result
5
+ */
6
+ export function runAppleScript(script: string): string {
7
+ try {
8
+ // Use osascript with heredoc to handle complex scripts
9
+ const result = execSync(`osascript <<'APPLESCRIPT'
10
+ ${script}
11
+ APPLESCRIPT`, {
12
+ encoding: "utf-8",
13
+ maxBuffer: 10 * 1024 * 1024, // 10MB buffer for large results
14
+ });
15
+ return result.trim();
16
+ } catch (error: any) {
17
+ throw new Error(`AppleScript error: ${error.message}`);
18
+ }
19
+ }
20
+
21
+ /**
22
+ * List all email accounts configured in Apple Mail
23
+ */
24
+ export function listAccounts(): string[] {
25
+ const script = `
26
+ tell application "Mail"
27
+ set accountList to {}
28
+ repeat with acc in accounts
29
+ set end of accountList to name of acc
30
+ end repeat
31
+ return accountList
32
+ end tell
33
+ `;
34
+ const result = runAppleScript(script);
35
+ if (!result) return [];
36
+
37
+ // AppleScript returns comma-separated list
38
+ return result.split(", ").map(s => s.trim()).filter(Boolean);
39
+ }
40
+
41
+ /**
42
+ * List mailboxes for a specific account or all accounts
43
+ */
44
+ export function listMailboxes(accountName?: string): { account: string; mailboxes: string[] }[] {
45
+ const script = accountName
46
+ ? `
47
+ tell application "Mail"
48
+ set results to {}
49
+ try
50
+ set acc to account "${accountName}"
51
+ set mailboxList to {}
52
+ repeat with mb in mailboxes of acc
53
+ set end of mailboxList to name of mb
54
+ end repeat
55
+ return mailboxList
56
+ on error
57
+ return {}
58
+ end try
59
+ end tell
60
+ `
61
+ : `
62
+ tell application "Mail"
63
+ set results to ""
64
+ repeat with acc in accounts
65
+ set accName to name of acc
66
+ set mailboxList to {}
67
+ repeat with mb in mailboxes of acc
68
+ set end of mailboxList to name of mb
69
+ end repeat
70
+ set results to results & accName & ":" & (mailboxList as string) & "|||"
71
+ end repeat
72
+ return results
73
+ end tell
74
+ `;
75
+
76
+ const result = runAppleScript(script);
77
+
78
+ if (accountName) {
79
+ // Single account result
80
+ const mailboxes = result ? result.split(", ").map(s => s.trim()).filter(Boolean) : [];
81
+ return [{ account: accountName, mailboxes }];
82
+ }
83
+
84
+ // Multiple accounts result
85
+ const accountResults: { account: string; mailboxes: string[] }[] = [];
86
+ const parts = result.split("|||").filter(Boolean);
87
+
88
+ for (const part of parts) {
89
+ const [accName, ...mailboxParts] = part.split(":");
90
+ const mailboxes = mailboxParts.join(":").split(", ").map(s => s.trim()).filter(Boolean);
91
+ if (accName) {
92
+ accountResults.push({ account: accName.trim(), mailboxes });
93
+ }
94
+ }
95
+
96
+ return accountResults;
97
+ }
98
+
99
+ export interface Email {
100
+ id: number;
101
+ subject: string;
102
+ sender: string;
103
+ dateSent: string;
104
+ isRead: boolean;
105
+ content?: string;
106
+ }
107
+
108
+ /**
109
+ * Get emails from a mailbox
110
+ */
111
+ export function getEmails(options: {
112
+ account?: string;
113
+ mailbox?: string;
114
+ limit?: number;
115
+ includeContent?: boolean;
116
+ }): Email[] {
117
+ const { account, mailbox = "INBOX", limit = 10, includeContent = false } = options;
118
+
119
+ const contentPart = includeContent
120
+ ? `set msgContent to content of msg`
121
+ : `set msgContent to ""`;
122
+
123
+ const accountPart = account
124
+ ? `mailbox "${mailbox}" of account "${account}"`
125
+ : `mailbox "${mailbox}"`;
126
+
127
+ const script = `
128
+ tell application "Mail"
129
+ set results to ""
130
+ try
131
+ set theMailbox to ${accountPart}
132
+ set msgList to messages of theMailbox
133
+ set msgCount to count of msgList
134
+ if msgCount > ${limit} then set msgCount to ${limit}
135
+
136
+ repeat with i from 1 to msgCount
137
+ set msg to item i of msgList
138
+ set msgId to id of msg
139
+ set msgSubject to subject of msg
140
+ set msgSender to sender of msg
141
+ set msgDate to date sent of msg
142
+ set msgRead to read status of msg
143
+ ${contentPart}
144
+
145
+ set results to results & msgId & "<<>>" & msgSubject & "<<>>" & msgSender & "<<>>" & (msgDate as string) & "<<>>" & msgRead & "<<>>" & msgContent & "|||"
146
+ end repeat
147
+ on error errMsg
148
+ return "ERROR:" & errMsg
149
+ end try
150
+ return results
151
+ end tell
152
+ `;
153
+
154
+ const result = runAppleScript(script);
155
+
156
+ if (result.startsWith("ERROR:")) {
157
+ throw new Error(result.substring(6));
158
+ }
159
+
160
+ const emails: Email[] = [];
161
+ const parts = result.split("|||").filter(Boolean);
162
+
163
+ for (const part of parts) {
164
+ const [id, subject, sender, dateSent, isRead, content] = part.split("<<>>");
165
+ emails.push({
166
+ id: parseInt(id) || 0,
167
+ subject: subject || "(No Subject)",
168
+ sender: sender || "(Unknown)",
169
+ dateSent: dateSent || "",
170
+ isRead: isRead === "true",
171
+ content: content || undefined,
172
+ });
173
+ }
174
+
175
+ return emails;
176
+ }
177
+
178
+ /**
179
+ * Search emails by query
180
+ */
181
+ export function searchEmails(options: {
182
+ query: string;
183
+ account?: string;
184
+ mailbox?: string;
185
+ limit?: number;
186
+ }): Email[] {
187
+ const { query, account, mailbox, limit = 10 } = options;
188
+
189
+ // Build the mailbox selection part
190
+ let mailboxPart: string;
191
+ if (account && mailbox) {
192
+ mailboxPart = `{mailbox "${mailbox}" of account "${account}"}`;
193
+ } else if (account) {
194
+ mailboxPart = `mailboxes of account "${account}"`;
195
+ } else if (mailbox) {
196
+ mailboxPart = `{mailbox "${mailbox}"}`;
197
+ } else {
198
+ mailboxPart = `inbox`;
199
+ }
200
+
201
+ const script = `
202
+ tell application "Mail"
203
+ set results to ""
204
+ set foundCount to 0
205
+ set searchQuery to "${query.replace(/"/g, '\\"')}"
206
+
207
+ try
208
+ set searchMailboxes to ${mailboxPart}
209
+ repeat with mb in searchMailboxes
210
+ if foundCount >= ${limit} then exit repeat
211
+
212
+ set msgList to (messages of mb whose subject contains searchQuery or sender contains searchQuery or content contains searchQuery)
213
+ repeat with msg in msgList
214
+ if foundCount >= ${limit} then exit repeat
215
+
216
+ set msgId to id of msg
217
+ set msgSubject to subject of msg
218
+ set msgSender to sender of msg
219
+ set msgDate to date sent of msg
220
+ set msgRead to read status of msg
221
+
222
+ set results to results & msgId & "<<>>" & msgSubject & "<<>>" & msgSender & "<<>>" & (msgDate as string) & "<<>>" & msgRead & "|||"
223
+ set foundCount to foundCount + 1
224
+ end repeat
225
+ end repeat
226
+ on error errMsg
227
+ return "ERROR:" & errMsg
228
+ end try
229
+ return results
230
+ end tell
231
+ `;
232
+
233
+ const result = runAppleScript(script);
234
+
235
+ if (result.startsWith("ERROR:")) {
236
+ throw new Error(result.substring(6));
237
+ }
238
+
239
+ const emails: Email[] = [];
240
+ const parts = result.split("|||").filter(Boolean);
241
+
242
+ for (const part of parts) {
243
+ const [id, subject, sender, dateSent, isRead] = part.split("<<>>");
244
+ emails.push({
245
+ id: parseInt(id) || 0,
246
+ subject: subject || "(No Subject)",
247
+ sender: sender || "(Unknown)",
248
+ dateSent: dateSent || "",
249
+ isRead: isRead === "true",
250
+ });
251
+ }
252
+
253
+ return emails;
254
+ }
255
+
256
+ /**
257
+ * Get unread email count
258
+ */
259
+ export function getUnreadCount(options: {
260
+ account?: string;
261
+ mailbox?: string;
262
+ }): number {
263
+ const { account, mailbox } = options;
264
+
265
+ let script: string;
266
+
267
+ if (account && mailbox) {
268
+ script = `
269
+ tell application "Mail"
270
+ return unread count of mailbox "${mailbox}" of account "${account}"
271
+ end tell
272
+ `;
273
+ } else if (account) {
274
+ script = `
275
+ tell application "Mail"
276
+ set total to 0
277
+ repeat with mb in mailboxes of account "${account}"
278
+ set total to total + (unread count of mb)
279
+ end repeat
280
+ return total
281
+ end tell
282
+ `;
283
+ } else if (mailbox) {
284
+ script = `
285
+ tell application "Mail"
286
+ set total to 0
287
+ repeat with acc in accounts
288
+ try
289
+ set total to total + (unread count of mailbox "${mailbox}" of acc)
290
+ end try
291
+ end repeat
292
+ return total
293
+ end tell
294
+ `;
295
+ } else {
296
+ script = `
297
+ tell application "Mail"
298
+ set total to 0
299
+ repeat with acc in accounts
300
+ repeat with mb in mailboxes of acc
301
+ set total to total + (unread count of mb)
302
+ end repeat
303
+ end repeat
304
+ return total
305
+ end tell
306
+ `;
307
+ }
308
+
309
+ const result = runAppleScript(script);
310
+ return parseInt(result) || 0;
311
+ }
312
+
313
+ /**
314
+ * Send an email
315
+ */
316
+ export function sendEmail(options: {
317
+ to: string | string[];
318
+ subject: string;
319
+ body: string;
320
+ cc?: string | string[];
321
+ bcc?: string | string[];
322
+ from?: string;
323
+ }): { success: boolean; message: string } {
324
+ const { to, subject, body, cc, bcc, from } = options;
325
+
326
+ const toList = Array.isArray(to) ? to : [to];
327
+ const ccList = cc ? (Array.isArray(cc) ? cc : [cc]) : [];
328
+ const bccList = bcc ? (Array.isArray(bcc) ? bcc : [bcc]) : [];
329
+
330
+ // Build recipient parts
331
+ const toRecipients = toList.map(addr => `make new to recipient at end of to recipients with properties {address:"${addr}"}`).join("\n ");
332
+ const ccRecipients = ccList.map(addr => `make new cc recipient at end of cc recipients with properties {address:"${addr}"}`).join("\n ");
333
+ const bccRecipients = bccList.map(addr => `make new bcc recipient at end of bcc recipients with properties {address:"${addr}"}`).join("\n ");
334
+
335
+ const fromPart = from ? `, sender:"${from}"` : "";
336
+
337
+ const script = `
338
+ tell application "Mail"
339
+ set newMessage to make new outgoing message with properties {subject:"${subject.replace(/"/g, '\\"')}", content:"${body.replace(/"/g, '\\"').replace(/\n/g, "\\n")}"${fromPart}}
340
+ tell newMessage
341
+ ${toRecipients}
342
+ ${ccRecipients ? ccRecipients : ""}
343
+ ${bccRecipients ? bccRecipients : ""}
344
+ end tell
345
+ send newMessage
346
+ return "Message sent successfully"
347
+ end tell
348
+ `;
349
+
350
+ try {
351
+ const result = runAppleScript(script);
352
+ return { success: true, message: result || "Message sent successfully" };
353
+ } catch (error: any) {
354
+ return { success: false, message: error.message };
355
+ }
356
+ }
package/src/index.ts ADDED
@@ -0,0 +1,176 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import {
6
+ CallToolRequestSchema,
7
+ ListToolsRequestSchema,
8
+ } from "@modelcontextprotocol/sdk/types.js";
9
+
10
+ import { tools } from "./tools.js";
11
+ import {
12
+ listAccounts,
13
+ listMailboxes,
14
+ getEmails,
15
+ searchEmails,
16
+ getUnreadCount,
17
+ sendEmail,
18
+ } from "./applescript/mail.js";
19
+
20
+ // Create MCP server
21
+ const server = new Server(
22
+ {
23
+ name: "apple-mail-mcp",
24
+ version: "1.0.0",
25
+ },
26
+ {
27
+ capabilities: {
28
+ tools: {},
29
+ },
30
+ }
31
+ );
32
+
33
+ // Handler for listing available tools
34
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
35
+ return { tools };
36
+ });
37
+
38
+ // Handler for tool calls
39
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
40
+ const { name, arguments: args } = request.params;
41
+
42
+ try {
43
+ switch (name) {
44
+ case "mail_list_accounts": {
45
+ const accounts = listAccounts();
46
+ return {
47
+ content: [
48
+ {
49
+ type: "text",
50
+ text: JSON.stringify({ accounts }, null, 2),
51
+ },
52
+ ],
53
+ };
54
+ }
55
+
56
+ case "mail_list_mailboxes": {
57
+ const account = args?.account as string | undefined;
58
+ const result = listMailboxes(account);
59
+ return {
60
+ content: [
61
+ {
62
+ type: "text",
63
+ text: JSON.stringify({ mailboxes: result }, null, 2),
64
+ },
65
+ ],
66
+ };
67
+ }
68
+
69
+ case "mail_get_emails": {
70
+ const emails = getEmails({
71
+ account: args?.account as string | undefined,
72
+ mailbox: (args?.mailbox as string) || "INBOX",
73
+ limit: (args?.limit as number) || 10,
74
+ includeContent: (args?.includeContent as boolean) || false,
75
+ });
76
+ return {
77
+ content: [
78
+ {
79
+ type: "text",
80
+ text: JSON.stringify({ emails, count: emails.length }, null, 2),
81
+ },
82
+ ],
83
+ };
84
+ }
85
+
86
+ case "mail_search": {
87
+ const query = args?.query as string;
88
+ if (!query) {
89
+ throw new Error("Search query is required");
90
+ }
91
+ const emails = searchEmails({
92
+ query,
93
+ account: args?.account as string | undefined,
94
+ mailbox: args?.mailbox as string | undefined,
95
+ limit: (args?.limit as number) || 10,
96
+ });
97
+ return {
98
+ content: [
99
+ {
100
+ type: "text",
101
+ text: JSON.stringify({ emails, count: emails.length, query }, null, 2),
102
+ },
103
+ ],
104
+ };
105
+ }
106
+
107
+ case "mail_get_unread_count": {
108
+ const count = getUnreadCount({
109
+ account: args?.account as string | undefined,
110
+ mailbox: args?.mailbox as string | undefined,
111
+ });
112
+ return {
113
+ content: [
114
+ {
115
+ type: "text",
116
+ text: JSON.stringify({ unreadCount: count }, null, 2),
117
+ },
118
+ ],
119
+ };
120
+ }
121
+
122
+ case "mail_send": {
123
+ const to = args?.to as string | string[];
124
+ const subject = args?.subject as string;
125
+ const body = args?.body as string;
126
+
127
+ if (!to || !subject || !body) {
128
+ throw new Error("Required fields: to, subject, body");
129
+ }
130
+
131
+ const result = sendEmail({
132
+ to,
133
+ subject,
134
+ body,
135
+ cc: args?.cc as string | string[] | undefined,
136
+ bcc: args?.bcc as string | string[] | undefined,
137
+ from: args?.from as string | undefined,
138
+ });
139
+
140
+ return {
141
+ content: [
142
+ {
143
+ type: "text",
144
+ text: JSON.stringify(result, null, 2),
145
+ },
146
+ ],
147
+ };
148
+ }
149
+
150
+ default:
151
+ throw new Error(`Unknown tool: ${name}`);
152
+ }
153
+ } catch (error: any) {
154
+ return {
155
+ content: [
156
+ {
157
+ type: "text",
158
+ text: JSON.stringify({ error: error.message }, null, 2),
159
+ },
160
+ ],
161
+ isError: true,
162
+ };
163
+ }
164
+ });
165
+
166
+ // Start the server
167
+ async function main() {
168
+ const transport = new StdioServerTransport();
169
+ await server.connect(transport);
170
+ console.error("Apple Mail MCP server running on stdio");
171
+ }
172
+
173
+ main().catch((error) => {
174
+ console.error("Fatal error:", error);
175
+ process.exit(1);
176
+ });
package/src/tools.ts ADDED
@@ -0,0 +1,156 @@
1
+ import { Tool } from "@modelcontextprotocol/sdk/types.js";
2
+
3
+ export const MAIL_LIST_ACCOUNTS: Tool = {
4
+ name: "mail_list_accounts",
5
+ description: "List all email accounts configured in Apple Mail",
6
+ inputSchema: {
7
+ type: "object",
8
+ properties: {},
9
+ required: [],
10
+ },
11
+ };
12
+
13
+ export const MAIL_LIST_MAILBOXES: Tool = {
14
+ name: "mail_list_mailboxes",
15
+ description: "List all mailboxes (folders) for a specific account or all accounts in Apple Mail",
16
+ inputSchema: {
17
+ type: "object",
18
+ properties: {
19
+ account: {
20
+ type: "string",
21
+ description: "The name of the email account to list mailboxes for. If not provided, lists mailboxes for all accounts.",
22
+ },
23
+ },
24
+ required: [],
25
+ },
26
+ };
27
+
28
+ export const MAIL_GET_EMAILS: Tool = {
29
+ name: "mail_get_emails",
30
+ description: "Get recent emails from a mailbox in Apple Mail",
31
+ inputSchema: {
32
+ type: "object",
33
+ properties: {
34
+ account: {
35
+ type: "string",
36
+ description: "The name of the email account",
37
+ },
38
+ mailbox: {
39
+ type: "string",
40
+ description: "The name of the mailbox/folder (default: INBOX)",
41
+ default: "INBOX",
42
+ },
43
+ limit: {
44
+ type: "number",
45
+ description: "Maximum number of emails to retrieve (default: 10)",
46
+ default: 10,
47
+ },
48
+ includeContent: {
49
+ type: "boolean",
50
+ description: "Whether to include the email body content (default: false)",
51
+ default: false,
52
+ },
53
+ },
54
+ required: [],
55
+ },
56
+ };
57
+
58
+ export const MAIL_SEARCH: Tool = {
59
+ name: "mail_search",
60
+ description: "Search emails in Apple Mail by subject, sender, or content",
61
+ inputSchema: {
62
+ type: "object",
63
+ properties: {
64
+ query: {
65
+ type: "string",
66
+ description: "The search query to match against email subject, sender, or content",
67
+ },
68
+ account: {
69
+ type: "string",
70
+ description: "The name of the email account to search in",
71
+ },
72
+ mailbox: {
73
+ type: "string",
74
+ description: "The name of the mailbox/folder to search in",
75
+ },
76
+ limit: {
77
+ type: "number",
78
+ description: "Maximum number of emails to return (default: 10)",
79
+ default: 10,
80
+ },
81
+ },
82
+ required: ["query"],
83
+ },
84
+ };
85
+
86
+ export const MAIL_GET_UNREAD_COUNT: Tool = {
87
+ name: "mail_get_unread_count",
88
+ description: "Get the count of unread emails in Apple Mail",
89
+ inputSchema: {
90
+ type: "object",
91
+ properties: {
92
+ account: {
93
+ type: "string",
94
+ description: "The name of the email account",
95
+ },
96
+ mailbox: {
97
+ type: "string",
98
+ description: "The name of the mailbox/folder",
99
+ },
100
+ },
101
+ required: [],
102
+ },
103
+ };
104
+
105
+ export const MAIL_SEND: Tool = {
106
+ name: "mail_send",
107
+ description: "Send an email using Apple Mail",
108
+ inputSchema: {
109
+ type: "object",
110
+ properties: {
111
+ to: {
112
+ oneOf: [
113
+ { type: "string" },
114
+ { type: "array", items: { type: "string" } },
115
+ ],
116
+ description: "Email address(es) of the recipient(s)",
117
+ },
118
+ subject: {
119
+ type: "string",
120
+ description: "The email subject line",
121
+ },
122
+ body: {
123
+ type: "string",
124
+ description: "The email body content",
125
+ },
126
+ cc: {
127
+ oneOf: [
128
+ { type: "string" },
129
+ { type: "array", items: { type: "string" } },
130
+ ],
131
+ description: "Email address(es) for CC recipients",
132
+ },
133
+ bcc: {
134
+ oneOf: [
135
+ { type: "string" },
136
+ { type: "array", items: { type: "string" } },
137
+ ],
138
+ description: "Email address(es) for BCC recipients",
139
+ },
140
+ from: {
141
+ type: "string",
142
+ description: "The sender email address (must be a configured account)",
143
+ },
144
+ },
145
+ required: ["to", "subject", "body"],
146
+ },
147
+ };
148
+
149
+ export const tools: Tool[] = [
150
+ MAIL_LIST_ACCOUNTS,
151
+ MAIL_LIST_MAILBOXES,
152
+ MAIL_GET_EMAILS,
153
+ MAIL_SEARCH,
154
+ MAIL_GET_UNREAD_COUNT,
155
+ MAIL_SEND,
156
+ ];
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "esModuleInterop": true,
7
+ "strict": true,
8
+ "skipLibCheck": true,
9
+ "outDir": "dist",
10
+ "declaration": true,
11
+ "types": ["node"]
12
+ },
13
+ "include": ["src/**/*"],
14
+ "exclude": ["node_modules", "dist"]
15
+ }