apple-mcp-mail 1.0.1__tar.gz
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.
- apple_mcp_mail-1.0.1/LICENSE +21 -0
- apple_mcp_mail-1.0.1/PKG-INFO +157 -0
- apple_mcp_mail-1.0.1/README.md +127 -0
- apple_mcp_mail-1.0.1/pyproject.toml +61 -0
- apple_mcp_mail-1.0.1/setup.cfg +4 -0
- apple_mcp_mail-1.0.1/src/apple_mail_mcp/__init__.py +3 -0
- apple_mcp_mail-1.0.1/src/apple_mail_mcp/__main__.py +4 -0
- apple_mcp_mail-1.0.1/src/apple_mail_mcp/applescripts/compose_draft.applescript +225 -0
- apple_mcp_mail-1.0.1/src/apple_mail_mcp/applescripts/delete_message.applescript +54 -0
- apple_mcp_mail-1.0.1/src/apple_mail_mcp/applescripts/forward_message.applescript +243 -0
- apple_mcp_mail-1.0.1/src/apple_mail_mcp/applescripts/get_message.applescript +151 -0
- apple_mcp_mail-1.0.1/src/apple_mail_mcp/applescripts/list_mailboxes.applescript +57 -0
- apple_mcp_mail-1.0.1/src/apple_mail_mcp/applescripts/mark_message.applescript +60 -0
- apple_mcp_mail-1.0.1/src/apple_mail_mcp/applescripts/move_message.applescript +82 -0
- apple_mcp_mail-1.0.1/src/apple_mail_mcp/applescripts/reply_message.applescript +229 -0
- apple_mcp_mail-1.0.1/src/apple_mail_mcp/applescripts/search_messages.applescript +136 -0
- apple_mcp_mail-1.0.1/src/apple_mail_mcp/applescripts/send_message.applescript +214 -0
- apple_mcp_mail-1.0.1/src/apple_mail_mcp/config.py +74 -0
- apple_mcp_mail-1.0.1/src/apple_mail_mcp/mail_bridge.py +318 -0
- apple_mcp_mail-1.0.1/src/apple_mail_mcp/models.py +276 -0
- apple_mcp_mail-1.0.1/src/apple_mail_mcp/permissions.py +56 -0
- apple_mcp_mail-1.0.1/src/apple_mail_mcp/tools.py +1043 -0
- apple_mcp_mail-1.0.1/src/apple_mcp_mail.egg-info/PKG-INFO +157 -0
- apple_mcp_mail-1.0.1/src/apple_mcp_mail.egg-info/SOURCES.txt +30 -0
- apple_mcp_mail-1.0.1/src/apple_mcp_mail.egg-info/dependency_links.txt +1 -0
- apple_mcp_mail-1.0.1/src/apple_mcp_mail.egg-info/entry_points.txt +3 -0
- apple_mcp_mail-1.0.1/src/apple_mcp_mail.egg-info/requires.txt +7 -0
- apple_mcp_mail-1.0.1/src/apple_mcp_mail.egg-info/top_level.txt +1 -0
- apple_mcp_mail-1.0.1/tests/test_config.py +18 -0
- apple_mcp_mail-1.0.1/tests/test_mail_bridge.py +45 -0
- apple_mcp_mail-1.0.1/tests/test_permissions.py +16 -0
- apple_mcp_mail-1.0.1/tests/test_tools.py +361 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Apple MCP Contributors
|
|
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.
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: apple-mcp-mail
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: Local MCP server for Apple Mail on macOS
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/JonathanRReed/Apple-MCPs
|
|
7
|
+
Project-URL: Repository, https://github.com/JonathanRReed/Apple-MCPs
|
|
8
|
+
Project-URL: Changelog, https://github.com/JonathanRReed/Apple-MCPs/blob/main/CHANGELOG.md
|
|
9
|
+
Project-URL: Issues, https://github.com/JonathanRReed/Apple-MCPs/issues
|
|
10
|
+
Keywords: mcp,model-context-protocol,macos,apple,ai-agent,automation
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Environment :: MacOS X
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: MacOS
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: apple-mcp-common<2,>=1.0.0
|
|
24
|
+
Requires-Dist: mcp<3,>=2.0.0
|
|
25
|
+
Requires-Dist: pydantic>=2.12.0
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest<10,>=8; extra == "dev"
|
|
28
|
+
Requires-Dist: ruff<1,>=0.12; extra == "dev"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
<!-- mcp-name: io.github.jonathanrreed/apple-mcp-mail -->
|
|
32
|
+
|
|
33
|
+
# Apple Mail MCP
|
|
34
|
+
|
|
35
|
+
MCP server for Apple Mail on macOS.
|
|
36
|
+
|
|
37
|
+
Provides access to mailboxes, message search, reading, and composition. Keep Mail as the system of record while enabling agents to search, read, draft, and send email.
|
|
38
|
+
|
|
39
|
+
## When to use
|
|
40
|
+
|
|
41
|
+
- Mail-only workflows
|
|
42
|
+
- Tighter permissions than the all-in-one server
|
|
43
|
+
- Draft and send workflows through the native Mail app
|
|
44
|
+
|
|
45
|
+
## What It Does
|
|
46
|
+
|
|
47
|
+
- List mailboxes
|
|
48
|
+
- Search and read messages
|
|
49
|
+
- Create drafts and send messages
|
|
50
|
+
- Reply, forward, mark read/unread, move, and delete
|
|
51
|
+
- Tool discovery helpers `search_tools` and `get_tool_info` for context-constrained clients
|
|
52
|
+
- Thread helpers: `mail_get_thread`, `mail_reply_latest_in_thread`, `mail_archive_thread`
|
|
53
|
+
- Mailbox resources and reply-oriented prompts
|
|
54
|
+
- Health and permission checks: `mail_health`, `mail_permission_guide`, `mail_recheck_permissions`
|
|
55
|
+
|
|
56
|
+
## Tools
|
|
57
|
+
|
|
58
|
+
`mail_health`, `mail_permission_guide`, `mail_recheck_permissions`, `mail_list_mailboxes`, `mail_search_messages`, `mail_get_message`, `mail_get_thread`, `mail_compose_draft`, `mail_send_message`, `mail_reply_message`, `mail_forward_message`, `mail_mark_message`, `mail_move_message`, `mail_delete_message`, `mail_reply_latest_in_thread`, `mail_archive_thread`, `mail_list_prompts`, `mail_get_prompt`, plus the discovery helpers `search_tools` and `get_tool_info`.
|
|
59
|
+
|
|
60
|
+
## Install On This Mac
|
|
61
|
+
|
|
62
|
+
<details>
|
|
63
|
+
<summary>Quick start (uvx, from PyPI)</summary>
|
|
64
|
+
|
|
65
|
+
With [uv](https://docs.astral.sh/uv/getting-started/installation/) installed:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
uvx apple-mcp-mail
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
No clone, no venv management.
|
|
72
|
+
|
|
73
|
+
</details>
|
|
74
|
+
|
|
75
|
+
<details>
|
|
76
|
+
<summary>From a clone</summary>
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
git clone https://github.com/JonathanRReed/Apple-MCPs.git
|
|
80
|
+
cd Apple-MCPs
|
|
81
|
+
uv sync --all-packages
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
This builds one workspace environment with every server's entry point in `.venv/bin` (for example `.venv/bin/apple-mail-mcp`). You can also point an MCP client at `AppleMail-MCP/start.sh`, which prefers `uv run` and falls back to a plain venv bootstrap (Python 3.11+ required).
|
|
85
|
+
|
|
86
|
+
</details>
|
|
87
|
+
|
|
88
|
+
## Install In AI Agents
|
|
89
|
+
|
|
90
|
+
<details>
|
|
91
|
+
<summary>Generic MCP client config</summary>
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"mcpServers": {
|
|
96
|
+
"apple-mail": {
|
|
97
|
+
"command": "uvx",
|
|
98
|
+
"args": ["apple-mcp-mail"],
|
|
99
|
+
"env": {
|
|
100
|
+
"APPLE_MAIL_MCP_SAFETY_PROFILE": "safe_manage",
|
|
101
|
+
"APPLE_MAIL_MCP_VISIBLE_DRAFTS": "true"
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Running from a clone instead? Use `/path/to/Apple-MCPs/AppleMail-MCP/start.sh` as the command with empty `args`.
|
|
109
|
+
|
|
110
|
+
</details>
|
|
111
|
+
|
|
112
|
+
<details>
|
|
113
|
+
<summary>Claude Code example</summary>
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
claude mcp add --transport stdio --scope project apple-mail -- uvx apple-mcp-mail
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
</details>
|
|
120
|
+
|
|
121
|
+
## Safety Modes
|
|
122
|
+
|
|
123
|
+
- `safe_readonly`, read and search only
|
|
124
|
+
- `safe_manage`, read plus draft creation
|
|
125
|
+
- `full_access`, full Mail tool surface in this repo
|
|
126
|
+
|
|
127
|
+
## Transport
|
|
128
|
+
|
|
129
|
+
`stdio` is the default and recommended transport. Set `APPLE_MAIL_MCP_TRANSPORT=streamable-http` (with optional `APPLE_MAIL_MCP_HOST` and `APPLE_MAIL_MCP_PORT`) to serve Streamable HTTP instead.
|
|
130
|
+
|
|
131
|
+
## macOS Permissions
|
|
132
|
+
|
|
133
|
+
- Automation access to Mail is required
|
|
134
|
+
|
|
135
|
+
## Launch Checklist
|
|
136
|
+
|
|
137
|
+
- Add `uvx apple-mcp-mail` (or a clone's `AppleMail-MCP/start.sh`) to your MCP client
|
|
138
|
+
- Reload or reconnect the client so the Mail tool surface is loaded into context
|
|
139
|
+
- Call `mail_health` first to confirm the server is reachable
|
|
140
|
+
- If Mail automation is blocked, call `mail_permission_guide`
|
|
141
|
+
- After changing macOS permissions, call `mail_recheck_permissions`
|
|
142
|
+
|
|
143
|
+
## Prompting Notes
|
|
144
|
+
|
|
145
|
+
- Run Contacts before any send or reply when the user identifies a person rather than an email address.
|
|
146
|
+
- `tools/list` returns the full Mail tool surface. Context-constrained clients can call `search_tools` first, then `get_tool_info` for the exact Mail tool they plan to call.
|
|
147
|
+
- `mail_search_messages` requires a query string. Use a sender, a subject fragment, or `*` as a wildcard.
|
|
148
|
+
- There is no list-all recent-mail endpoint.
|
|
149
|
+
- Use `mail_get_thread` when the user means a conversation, not a single message.
|
|
150
|
+
- Use `mail_reply_latest_in_thread` when the agent should reply to the newest message in the conversation.
|
|
151
|
+
- Use `mail_archive_thread` when the user wants thread-level cleanup and Archive is the intended mailbox.
|
|
152
|
+
- If the user could mean text or email, ask once before choosing Messages or Mail.
|
|
153
|
+
- When Mail must send from a specific identity, pass the exact sender email in `from_account`.
|
|
154
|
+
|
|
155
|
+
## Related
|
|
156
|
+
|
|
157
|
+
- [Apple-Tools-MCP](../Apple-Tools-MCP/README.md)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<!-- mcp-name: io.github.jonathanrreed/apple-mcp-mail -->
|
|
2
|
+
|
|
3
|
+
# Apple Mail MCP
|
|
4
|
+
|
|
5
|
+
MCP server for Apple Mail on macOS.
|
|
6
|
+
|
|
7
|
+
Provides access to mailboxes, message search, reading, and composition. Keep Mail as the system of record while enabling agents to search, read, draft, and send email.
|
|
8
|
+
|
|
9
|
+
## When to use
|
|
10
|
+
|
|
11
|
+
- Mail-only workflows
|
|
12
|
+
- Tighter permissions than the all-in-one server
|
|
13
|
+
- Draft and send workflows through the native Mail app
|
|
14
|
+
|
|
15
|
+
## What It Does
|
|
16
|
+
|
|
17
|
+
- List mailboxes
|
|
18
|
+
- Search and read messages
|
|
19
|
+
- Create drafts and send messages
|
|
20
|
+
- Reply, forward, mark read/unread, move, and delete
|
|
21
|
+
- Tool discovery helpers `search_tools` and `get_tool_info` for context-constrained clients
|
|
22
|
+
- Thread helpers: `mail_get_thread`, `mail_reply_latest_in_thread`, `mail_archive_thread`
|
|
23
|
+
- Mailbox resources and reply-oriented prompts
|
|
24
|
+
- Health and permission checks: `mail_health`, `mail_permission_guide`, `mail_recheck_permissions`
|
|
25
|
+
|
|
26
|
+
## Tools
|
|
27
|
+
|
|
28
|
+
`mail_health`, `mail_permission_guide`, `mail_recheck_permissions`, `mail_list_mailboxes`, `mail_search_messages`, `mail_get_message`, `mail_get_thread`, `mail_compose_draft`, `mail_send_message`, `mail_reply_message`, `mail_forward_message`, `mail_mark_message`, `mail_move_message`, `mail_delete_message`, `mail_reply_latest_in_thread`, `mail_archive_thread`, `mail_list_prompts`, `mail_get_prompt`, plus the discovery helpers `search_tools` and `get_tool_info`.
|
|
29
|
+
|
|
30
|
+
## Install On This Mac
|
|
31
|
+
|
|
32
|
+
<details>
|
|
33
|
+
<summary>Quick start (uvx, from PyPI)</summary>
|
|
34
|
+
|
|
35
|
+
With [uv](https://docs.astral.sh/uv/getting-started/installation/) installed:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
uvx apple-mcp-mail
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
No clone, no venv management.
|
|
42
|
+
|
|
43
|
+
</details>
|
|
44
|
+
|
|
45
|
+
<details>
|
|
46
|
+
<summary>From a clone</summary>
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
git clone https://github.com/JonathanRReed/Apple-MCPs.git
|
|
50
|
+
cd Apple-MCPs
|
|
51
|
+
uv sync --all-packages
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
This builds one workspace environment with every server's entry point in `.venv/bin` (for example `.venv/bin/apple-mail-mcp`). You can also point an MCP client at `AppleMail-MCP/start.sh`, which prefers `uv run` and falls back to a plain venv bootstrap (Python 3.11+ required).
|
|
55
|
+
|
|
56
|
+
</details>
|
|
57
|
+
|
|
58
|
+
## Install In AI Agents
|
|
59
|
+
|
|
60
|
+
<details>
|
|
61
|
+
<summary>Generic MCP client config</summary>
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"mcpServers": {
|
|
66
|
+
"apple-mail": {
|
|
67
|
+
"command": "uvx",
|
|
68
|
+
"args": ["apple-mcp-mail"],
|
|
69
|
+
"env": {
|
|
70
|
+
"APPLE_MAIL_MCP_SAFETY_PROFILE": "safe_manage",
|
|
71
|
+
"APPLE_MAIL_MCP_VISIBLE_DRAFTS": "true"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Running from a clone instead? Use `/path/to/Apple-MCPs/AppleMail-MCP/start.sh` as the command with empty `args`.
|
|
79
|
+
|
|
80
|
+
</details>
|
|
81
|
+
|
|
82
|
+
<details>
|
|
83
|
+
<summary>Claude Code example</summary>
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
claude mcp add --transport stdio --scope project apple-mail -- uvx apple-mcp-mail
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
</details>
|
|
90
|
+
|
|
91
|
+
## Safety Modes
|
|
92
|
+
|
|
93
|
+
- `safe_readonly`, read and search only
|
|
94
|
+
- `safe_manage`, read plus draft creation
|
|
95
|
+
- `full_access`, full Mail tool surface in this repo
|
|
96
|
+
|
|
97
|
+
## Transport
|
|
98
|
+
|
|
99
|
+
`stdio` is the default and recommended transport. Set `APPLE_MAIL_MCP_TRANSPORT=streamable-http` (with optional `APPLE_MAIL_MCP_HOST` and `APPLE_MAIL_MCP_PORT`) to serve Streamable HTTP instead.
|
|
100
|
+
|
|
101
|
+
## macOS Permissions
|
|
102
|
+
|
|
103
|
+
- Automation access to Mail is required
|
|
104
|
+
|
|
105
|
+
## Launch Checklist
|
|
106
|
+
|
|
107
|
+
- Add `uvx apple-mcp-mail` (or a clone's `AppleMail-MCP/start.sh`) to your MCP client
|
|
108
|
+
- Reload or reconnect the client so the Mail tool surface is loaded into context
|
|
109
|
+
- Call `mail_health` first to confirm the server is reachable
|
|
110
|
+
- If Mail automation is blocked, call `mail_permission_guide`
|
|
111
|
+
- After changing macOS permissions, call `mail_recheck_permissions`
|
|
112
|
+
|
|
113
|
+
## Prompting Notes
|
|
114
|
+
|
|
115
|
+
- Run Contacts before any send or reply when the user identifies a person rather than an email address.
|
|
116
|
+
- `tools/list` returns the full Mail tool surface. Context-constrained clients can call `search_tools` first, then `get_tool_info` for the exact Mail tool they plan to call.
|
|
117
|
+
- `mail_search_messages` requires a query string. Use a sender, a subject fragment, or `*` as a wildcard.
|
|
118
|
+
- There is no list-all recent-mail endpoint.
|
|
119
|
+
- Use `mail_get_thread` when the user means a conversation, not a single message.
|
|
120
|
+
- Use `mail_reply_latest_in_thread` when the agent should reply to the newest message in the conversation.
|
|
121
|
+
- Use `mail_archive_thread` when the user wants thread-level cleanup and Archive is the intended mailbox.
|
|
122
|
+
- If the user could mean text or email, ask once before choosing Messages or Mail.
|
|
123
|
+
- When Mail must send from a specific identity, pass the exact sender email in `from_account`.
|
|
124
|
+
|
|
125
|
+
## Related
|
|
126
|
+
|
|
127
|
+
- [Apple-Tools-MCP](../Apple-Tools-MCP/README.md)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "apple-mcp-mail"
|
|
7
|
+
version = "1.0.1"
|
|
8
|
+
description = "Local MCP server for Apple Mail on macOS"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.11"
|
|
13
|
+
keywords = ["mcp", "model-context-protocol", "macos", "apple", "ai-agent", "automation"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 5 - Production/Stable",
|
|
16
|
+
"Environment :: MacOS X",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Operating System :: MacOS",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Programming Language :: Python :: 3.14",
|
|
23
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"apple-mcp-common>=1.0.0,<2",
|
|
27
|
+
"mcp>=2.0.0,<3",
|
|
28
|
+
"pydantic>=2.12.0",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://github.com/JonathanRReed/Apple-MCPs"
|
|
33
|
+
Repository = "https://github.com/JonathanRReed/Apple-MCPs"
|
|
34
|
+
Changelog = "https://github.com/JonathanRReed/Apple-MCPs/blob/main/CHANGELOG.md"
|
|
35
|
+
Issues = "https://github.com/JonathanRReed/Apple-MCPs/issues"
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
apple-mail-mcp = "apple_mail_mcp.tools:main"
|
|
39
|
+
apple-mcp-mail = "apple_mail_mcp.tools:main"
|
|
40
|
+
|
|
41
|
+
[project.optional-dependencies]
|
|
42
|
+
dev = [
|
|
43
|
+
"pytest>=8,<10",
|
|
44
|
+
"ruff>=0.12,<1",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[tool.setuptools]
|
|
48
|
+
package-dir = {"" = "src"}
|
|
49
|
+
|
|
50
|
+
[tool.setuptools.package-data]
|
|
51
|
+
apple_mail_mcp = ["applescripts/*.applescript"]
|
|
52
|
+
|
|
53
|
+
[tool.setuptools.packages.find]
|
|
54
|
+
where = ["src"]
|
|
55
|
+
|
|
56
|
+
[tool.pytest.ini_options]
|
|
57
|
+
pythonpath = ["src"]
|
|
58
|
+
testpaths = ["tests"]
|
|
59
|
+
|
|
60
|
+
[tool.uv.sources]
|
|
61
|
+
apple-mcp-common = { workspace = true }
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
on splitText(sourceText, separatorValue)
|
|
2
|
+
if sourceText is "" then
|
|
3
|
+
return {}
|
|
4
|
+
end if
|
|
5
|
+
set previousDelimiters to AppleScript's text item delimiters
|
|
6
|
+
set AppleScript's text item delimiters to separatorValue
|
|
7
|
+
set textParts to text items of sourceText
|
|
8
|
+
set AppleScript's text item delimiters to previousDelimiters
|
|
9
|
+
return textParts
|
|
10
|
+
end splitText
|
|
11
|
+
|
|
12
|
+
on replaceText(sourceText, findText, replacementText)
|
|
13
|
+
set previousDelimiters to AppleScript's text item delimiters
|
|
14
|
+
set AppleScript's text item delimiters to findText
|
|
15
|
+
set textParts to text items of sourceText
|
|
16
|
+
set AppleScript's text item delimiters to replacementText
|
|
17
|
+
set outputText to textParts as text
|
|
18
|
+
set AppleScript's text item delimiters to previousDelimiters
|
|
19
|
+
return outputText
|
|
20
|
+
end replaceText
|
|
21
|
+
|
|
22
|
+
on sanitizeText(valueText)
|
|
23
|
+
if valueText is missing value then
|
|
24
|
+
return ""
|
|
25
|
+
end if
|
|
26
|
+
set safeText to valueText as text
|
|
27
|
+
set safeText to my replaceText(safeText, return, "\\n")
|
|
28
|
+
set safeText to my replaceText(safeText, linefeed, "\\n")
|
|
29
|
+
set safeText to my replaceText(safeText, (ASCII character 31), " ")
|
|
30
|
+
set safeText to my replaceText(safeText, (ASCII character 30), " ")
|
|
31
|
+
set safeText to my replaceText(safeText, (ASCII character 29), " ")
|
|
32
|
+
return safeText
|
|
33
|
+
end sanitizeText
|
|
34
|
+
|
|
35
|
+
on normalizeText(valueText)
|
|
36
|
+
if valueText is missing value then
|
|
37
|
+
return ""
|
|
38
|
+
end if
|
|
39
|
+
set normalizedText to do shell script "/bin/echo " & quoted form of (valueText as text) & " | /usr/bin/tr '[:upper:]' '[:lower:]'"
|
|
40
|
+
return normalizedText
|
|
41
|
+
end normalizeText
|
|
42
|
+
|
|
43
|
+
on boolText(flagValue)
|
|
44
|
+
if flagValue then
|
|
45
|
+
return "true"
|
|
46
|
+
end if
|
|
47
|
+
return "false"
|
|
48
|
+
end boolText
|
|
49
|
+
|
|
50
|
+
on resolveAccountBySender(senderRaw)
|
|
51
|
+
if senderRaw is "" then
|
|
52
|
+
return missing value
|
|
53
|
+
end if
|
|
54
|
+
tell application "Mail"
|
|
55
|
+
repeat with theAccount in every account
|
|
56
|
+
try
|
|
57
|
+
if (name of theAccount as text) is senderRaw then
|
|
58
|
+
return theAccount
|
|
59
|
+
end if
|
|
60
|
+
end try
|
|
61
|
+
try
|
|
62
|
+
if (email addresses of theAccount) contains senderRaw then
|
|
63
|
+
return theAccount
|
|
64
|
+
end if
|
|
65
|
+
end try
|
|
66
|
+
end repeat
|
|
67
|
+
end tell
|
|
68
|
+
error "ACCOUNT_NOT_FOUND"
|
|
69
|
+
end resolveAccountBySender
|
|
70
|
+
|
|
71
|
+
on primaryEmailForAccount(theAccount)
|
|
72
|
+
tell application "Mail"
|
|
73
|
+
try
|
|
74
|
+
return item 1 of (email addresses of theAccount) as text
|
|
75
|
+
end try
|
|
76
|
+
end tell
|
|
77
|
+
return ""
|
|
78
|
+
end primaryEmailForAccount
|
|
79
|
+
|
|
80
|
+
on senderMatches(candidateText, senderRaw, senderEmail)
|
|
81
|
+
set normalizedCandidate to my normalizeText(candidateText)
|
|
82
|
+
if senderEmail is not "" then
|
|
83
|
+
set normalizedEmail to my normalizeText(senderEmail)
|
|
84
|
+
if normalizedCandidate contains normalizedEmail then
|
|
85
|
+
return true
|
|
86
|
+
end if
|
|
87
|
+
end if
|
|
88
|
+
if senderRaw is not "" then
|
|
89
|
+
set normalizedSender to my normalizeText(senderRaw)
|
|
90
|
+
if normalizedCandidate contains normalizedSender then
|
|
91
|
+
return true
|
|
92
|
+
end if
|
|
93
|
+
end if
|
|
94
|
+
return false
|
|
95
|
+
end senderMatches
|
|
96
|
+
|
|
97
|
+
on currentSenderMatches(senderRaw, senderEmail)
|
|
98
|
+
tell application "System Events"
|
|
99
|
+
tell process "Mail"
|
|
100
|
+
set frontmost to true
|
|
101
|
+
set composeWindow to front window
|
|
102
|
+
repeat with popupRef in every pop up button of composeWindow
|
|
103
|
+
try
|
|
104
|
+
set currentValue to value of popupRef as text
|
|
105
|
+
if my senderMatches(currentValue, senderRaw, senderEmail) then
|
|
106
|
+
return currentValue
|
|
107
|
+
end if
|
|
108
|
+
end try
|
|
109
|
+
end repeat
|
|
110
|
+
end tell
|
|
111
|
+
end tell
|
|
112
|
+
return ""
|
|
113
|
+
end currentSenderMatches
|
|
114
|
+
|
|
115
|
+
on selectSenderPopup(senderRaw, senderEmail)
|
|
116
|
+
tell application "Mail" to activate
|
|
117
|
+
repeat with attemptIndex from 1 to 5
|
|
118
|
+
delay 0.3
|
|
119
|
+
set currentMatch to my currentSenderMatches(senderRaw, senderEmail)
|
|
120
|
+
if currentMatch is not "" then
|
|
121
|
+
return currentMatch
|
|
122
|
+
end if
|
|
123
|
+
tell application "System Events"
|
|
124
|
+
tell process "Mail"
|
|
125
|
+
set frontmost to true
|
|
126
|
+
set composeWindow to front window
|
|
127
|
+
repeat with popupRef in every pop up button of composeWindow
|
|
128
|
+
click popupRef
|
|
129
|
+
delay 0.25
|
|
130
|
+
set matchingItem to missing value
|
|
131
|
+
try
|
|
132
|
+
repeat with menuItemRef in every menu item of menu 1 of popupRef
|
|
133
|
+
set itemName to name of menuItemRef as text
|
|
134
|
+
if my senderMatches(itemName, senderRaw, senderEmail) then
|
|
135
|
+
set matchingItem to menuItemRef
|
|
136
|
+
exit repeat
|
|
137
|
+
end if
|
|
138
|
+
end repeat
|
|
139
|
+
end try
|
|
140
|
+
if matchingItem is not missing value then
|
|
141
|
+
click matchingItem
|
|
142
|
+
delay 0.4
|
|
143
|
+
set updatedMatch to my currentSenderMatches(senderRaw, senderEmail)
|
|
144
|
+
if updatedMatch is not "" then
|
|
145
|
+
return updatedMatch
|
|
146
|
+
end if
|
|
147
|
+
end if
|
|
148
|
+
key code 53
|
|
149
|
+
delay 0.1
|
|
150
|
+
end repeat
|
|
151
|
+
end tell
|
|
152
|
+
end tell
|
|
153
|
+
end repeat
|
|
154
|
+
error "ACCOUNT_MENU_ITEM_NOT_FOUND"
|
|
155
|
+
end selectSenderPopup
|
|
156
|
+
|
|
157
|
+
on run argv
|
|
158
|
+
set toRaw to item 1 of argv
|
|
159
|
+
set ccRaw to item 2 of argv
|
|
160
|
+
set bccRaw to item 3 of argv
|
|
161
|
+
set subjectText to item 4 of argv
|
|
162
|
+
set bodyText to item 5 of argv
|
|
163
|
+
set attachmentRaw to item 6 of argv
|
|
164
|
+
set visibleText to item 7 of argv
|
|
165
|
+
set senderRaw to item 8 of argv
|
|
166
|
+
|
|
167
|
+
set fieldSeparator to ASCII character 31
|
|
168
|
+
set recordSeparator to ASCII character 30
|
|
169
|
+
set listSeparator to ASCII character 29
|
|
170
|
+
set toList to my splitText(toRaw, listSeparator)
|
|
171
|
+
set ccList to my splitText(ccRaw, listSeparator)
|
|
172
|
+
set bccList to my splitText(bccRaw, listSeparator)
|
|
173
|
+
set attachmentList to my splitText(attachmentRaw, listSeparator)
|
|
174
|
+
set shouldBeVisible to true
|
|
175
|
+
if visibleText is "false" then
|
|
176
|
+
set shouldBeVisible to false
|
|
177
|
+
end if
|
|
178
|
+
|
|
179
|
+
set resolvedAccountLabel to ""
|
|
180
|
+
set senderEmail to ""
|
|
181
|
+
|
|
182
|
+
tell application "Mail"
|
|
183
|
+
set effectiveVisible to shouldBeVisible
|
|
184
|
+
if senderRaw is not "" then
|
|
185
|
+
set effectiveVisible to true
|
|
186
|
+
end if
|
|
187
|
+
set newMessage to make new outgoing message with properties {visible:effectiveVisible, subject:subjectText, content:bodyText}
|
|
188
|
+
if senderRaw is not "" then
|
|
189
|
+
set matchedAccount to my resolveAccountBySender(senderRaw)
|
|
190
|
+
set senderEmail to my primaryEmailForAccount(matchedAccount)
|
|
191
|
+
set sender of newMessage to senderRaw
|
|
192
|
+
delay 0.2
|
|
193
|
+
end if
|
|
194
|
+
tell newMessage
|
|
195
|
+
repeat with addressText in toList
|
|
196
|
+
if (addressText as text) is not "" then
|
|
197
|
+
make new to recipient at end of to recipients with properties {address:addressText as text}
|
|
198
|
+
end if
|
|
199
|
+
end repeat
|
|
200
|
+
repeat with addressText in ccList
|
|
201
|
+
if (addressText as text) is not "" then
|
|
202
|
+
make new cc recipient at end of cc recipients with properties {address:addressText as text}
|
|
203
|
+
end if
|
|
204
|
+
end repeat
|
|
205
|
+
repeat with addressText in bccList
|
|
206
|
+
if (addressText as text) is not "" then
|
|
207
|
+
make new bcc recipient at end of bcc recipients with properties {address:addressText as text}
|
|
208
|
+
end if
|
|
209
|
+
end repeat
|
|
210
|
+
repeat with attachmentPath in attachmentList
|
|
211
|
+
if (attachmentPath as text) is not "" then
|
|
212
|
+
make new attachment with properties {file name:(POSIX file (attachmentPath as text))} at after the last paragraph
|
|
213
|
+
end if
|
|
214
|
+
end repeat
|
|
215
|
+
end tell
|
|
216
|
+
end tell
|
|
217
|
+
|
|
218
|
+
if senderRaw is not "" then
|
|
219
|
+
set resolvedAccountLabel to my sanitizeText(my selectSenderPopup(senderRaw, senderEmail))
|
|
220
|
+
end if
|
|
221
|
+
|
|
222
|
+
tell application "Mail"
|
|
223
|
+
return my sanitizeText((id of newMessage) as text) & fieldSeparator & my boolText(visible of newMessage) & fieldSeparator & resolvedAccountLabel & recordSeparator
|
|
224
|
+
end tell
|
|
225
|
+
end run
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
on boolText(flagValue)
|
|
2
|
+
if flagValue then
|
|
3
|
+
return "true"
|
|
4
|
+
end if
|
|
5
|
+
return "false"
|
|
6
|
+
end boolText
|
|
7
|
+
|
|
8
|
+
on run argv
|
|
9
|
+
set accountName to item 1 of argv
|
|
10
|
+
set mailboxName to item 2 of argv
|
|
11
|
+
set appleId to item 3 of argv
|
|
12
|
+
|
|
13
|
+
set fieldSeparator to ASCII character 31
|
|
14
|
+
set recordSeparator to ASCII character 30
|
|
15
|
+
|
|
16
|
+
tell application "Mail"
|
|
17
|
+
set targetAccount to missing value
|
|
18
|
+
repeat with theAccount in every account
|
|
19
|
+
if (name of theAccount) is accountName then
|
|
20
|
+
set targetAccount to theAccount
|
|
21
|
+
exit repeat
|
|
22
|
+
end if
|
|
23
|
+
end repeat
|
|
24
|
+
if targetAccount is missing value then
|
|
25
|
+
error "ACCOUNT_NOT_FOUND"
|
|
26
|
+
end if
|
|
27
|
+
|
|
28
|
+
set targetMailbox to missing value
|
|
29
|
+
repeat with theMailbox in every mailbox of targetAccount
|
|
30
|
+
if (name of theMailbox) is mailboxName then
|
|
31
|
+
set targetMailbox to theMailbox
|
|
32
|
+
exit repeat
|
|
33
|
+
end if
|
|
34
|
+
end repeat
|
|
35
|
+
if targetMailbox is missing value then
|
|
36
|
+
error "MAILBOX_NOT_FOUND"
|
|
37
|
+
end if
|
|
38
|
+
|
|
39
|
+
set targetMessage to missing value
|
|
40
|
+
repeat with theMessage in messages of targetMailbox
|
|
41
|
+
if ((id of theMessage) as text) is appleId then
|
|
42
|
+
set targetMessage to theMessage
|
|
43
|
+
exit repeat
|
|
44
|
+
end if
|
|
45
|
+
end repeat
|
|
46
|
+
if targetMessage is missing value then
|
|
47
|
+
error "MESSAGE_NOT_FOUND"
|
|
48
|
+
end if
|
|
49
|
+
|
|
50
|
+
delete targetMessage
|
|
51
|
+
|
|
52
|
+
return my boolText(true) & recordSeparator
|
|
53
|
+
end tell
|
|
54
|
+
end run
|