cpanel-mail-mcp 0.2.0__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.
- cpanel_mail_mcp-0.2.0/LICENSE +21 -0
- cpanel_mail_mcp-0.2.0/PKG-INFO +260 -0
- cpanel_mail_mcp-0.2.0/README.md +234 -0
- cpanel_mail_mcp-0.2.0/pyproject.toml +42 -0
- cpanel_mail_mcp-0.2.0/setup.cfg +4 -0
- cpanel_mail_mcp-0.2.0/src/cpanel_mail_mcp/__init__.py +5 -0
- cpanel_mail_mcp-0.2.0/src/cpanel_mail_mcp/__main__.py +4 -0
- cpanel_mail_mcp-0.2.0/src/cpanel_mail_mcp/accounts.py +103 -0
- cpanel_mail_mcp-0.2.0/src/cpanel_mail_mcp/ics.py +73 -0
- cpanel_mail_mcp-0.2.0/src/cpanel_mail_mcp/imap_ops.py +244 -0
- cpanel_mail_mcp-0.2.0/src/cpanel_mail_mcp/server.py +312 -0
- cpanel_mail_mcp-0.2.0/src/cpanel_mail_mcp/smtp_ops.py +188 -0
- cpanel_mail_mcp-0.2.0/src/cpanel_mail_mcp/utf7.py +55 -0
- cpanel_mail_mcp-0.2.0/src/cpanel_mail_mcp.egg-info/PKG-INFO +260 -0
- cpanel_mail_mcp-0.2.0/src/cpanel_mail_mcp.egg-info/SOURCES.txt +17 -0
- cpanel_mail_mcp-0.2.0/src/cpanel_mail_mcp.egg-info/dependency_links.txt +1 -0
- cpanel_mail_mcp-0.2.0/src/cpanel_mail_mcp.egg-info/entry_points.txt +2 -0
- cpanel_mail_mcp-0.2.0/src/cpanel_mail_mcp.egg-info/requires.txt +2 -0
- cpanel_mail_mcp-0.2.0/src/cpanel_mail_mcp.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rodrigo Sauceda
|
|
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,260 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cpanel-mail-mcp
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: MCP server for IMAP/SMTP email — multi-account, attachments, ICS calendar invites, drafts, Save-to-Sent.
|
|
5
|
+
Author-email: Rodrigo Sauceda <rodrigo.sauceda@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/rsauceda/cpanel-mail-mcp
|
|
8
|
+
Project-URL: Repository, https://github.com/rsauceda/cpanel-mail-mcp
|
|
9
|
+
Project-URL: Issues, https://github.com/rsauceda/cpanel-mail-mcp/issues
|
|
10
|
+
Keywords: mcp,email,imap,smtp,cpanel,calendar,ics,attachments
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Communications :: Email
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: mcp[cli]>=1.2.0
|
|
24
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# cpanel-mail-mcp
|
|
28
|
+
|
|
29
|
+
MCP server for IMAP/SMTP email accounts — works with cPanel, Gmail
|
|
30
|
+
(app passwords), Outlook, Fastmail, iCloud, or any provider that speaks
|
|
31
|
+
plain IMAP + SMTP.
|
|
32
|
+
|
|
33
|
+
## Features
|
|
34
|
+
|
|
35
|
+
* **Multi-account** — manage multiple email accounts from different providers
|
|
36
|
+
* **Read, search, list** — full IMAP support with folder browsing
|
|
37
|
+
* **Send emails** — plain text, HTML, or both (multipart/alternative)
|
|
38
|
+
* **Attachments** — send via file path or base64-encoded inline data
|
|
39
|
+
* **Download attachments** — extract attachments from received emails as base64
|
|
40
|
+
* **Calendar invites** — send proper ICS invitations with Accept/Decline buttons
|
|
41
|
+
* **Save to Sent** — automatically saves sent emails to the Sent folder via IMAP
|
|
42
|
+
* **Optional send gate** — configurable confirmation code to prevent accidental sends
|
|
43
|
+
* **International folders** — handles UTF-7 encoded folder names (German, etc.)
|
|
44
|
+
* **Compact MCP surface** — one `email` tool with lazy action discovery to reduce
|
|
45
|
+
client context use
|
|
46
|
+
|
|
47
|
+
## Install
|
|
48
|
+
|
|
49
|
+
### With `uvx` (recommended, no venv setup)
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
claude mcp add cpanel-mail \
|
|
53
|
+
-e CPANEL_USER=you@example.com \
|
|
54
|
+
-e CPANEL_PASS='your_password' \
|
|
55
|
+
-e CPANEL_SMTP_HOST=mail.example.com \
|
|
56
|
+
-e CPANEL_IMAP_HOST=mail.example.com \
|
|
57
|
+
-- uvx cpanel-mail-mcp
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### With `pipx`
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pipx install cpanel-mail-mcp
|
|
64
|
+
claude mcp add cpanel-mail \
|
|
65
|
+
-e CPANEL_USER=you@example.com -e CPANEL_PASS='...' \
|
|
66
|
+
-e CPANEL_SMTP_HOST=mail.example.com -e CPANEL_IMAP_HOST=mail.example.com \
|
|
67
|
+
-- cpanel-mail-mcp
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Development install (from a git checkout)
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
git clone https://github.com/rsauceda/cpanel-mail-mcp
|
|
74
|
+
cd cpanel-mail-mcp
|
|
75
|
+
python -m venv .venv && source .venv/bin/activate
|
|
76
|
+
pip install -e .
|
|
77
|
+
cp .env.example .env # edit
|
|
78
|
+
cpanel-mail-mcp # runs the stdio server
|
|
79
|
+
# or: python -m cpanel_mail_mcp
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Configuration
|
|
83
|
+
|
|
84
|
+
Three ways, in priority order:
|
|
85
|
+
|
|
86
|
+
### 1. `EMAIL_ACCOUNTS_JSON` (best for multi-account)
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
export EMAIL_ACCOUNTS_JSON='[
|
|
90
|
+
{"name":"work","user":"me@work.com","password":"...",
|
|
91
|
+
"smtp_host":"mail.work.com","imap_host":"mail.work.com",
|
|
92
|
+
"sent_folder":"INBOX.Sent"},
|
|
93
|
+
{"name":"gmail","user":"me@gmail.com","password":"app_pass",
|
|
94
|
+
"smtp_host":"smtp.gmail.com","smtp_port":587,
|
|
95
|
+
"imap_host":"imap.gmail.com","sent_folder":"[Gmail]/Sent Mail"}
|
|
96
|
+
]'
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 2. `EMAIL_ACCOUNTS_FILE`
|
|
100
|
+
|
|
101
|
+
Point to a JSON file with the same shape.
|
|
102
|
+
|
|
103
|
+
### 3. Legacy single-account (`CPANEL_*`)
|
|
104
|
+
|
|
105
|
+
Backwards compatible with 0.1.x installs. See [`.env.example`](.env.example).
|
|
106
|
+
|
|
107
|
+
### Account fields
|
|
108
|
+
|
|
109
|
+
| field | required | default |
|
|
110
|
+
|-----------------|----------|-----------|
|
|
111
|
+
| `name` | no | `user` |
|
|
112
|
+
| `user` | **yes** | — |
|
|
113
|
+
| `password` | **yes** | — |
|
|
114
|
+
| `smtp_host` | **yes** | — |
|
|
115
|
+
| `smtp_port` | no | `465` |
|
|
116
|
+
| `imap_host` | **yes** | — |
|
|
117
|
+
| `imap_port` | no | `993` |
|
|
118
|
+
| `sent_folder` | no | `Sent` |
|
|
119
|
+
| `drafts_folder` | no | `Drafts` |
|
|
120
|
+
| `save_to_sent` | no | `true` |
|
|
121
|
+
| `from_name` | no | — |
|
|
122
|
+
|
|
123
|
+
### Loading a dev `.env`
|
|
124
|
+
|
|
125
|
+
The server auto-loads `.env` from the current working directory. Point it
|
|
126
|
+
elsewhere with:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
export EMAIL_ENV_FILE=/absolute/path/to/.env
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Usage — the `email` tool
|
|
133
|
+
|
|
134
|
+
The server exposes **one** MCP tool named `email`. Call it with an `action`
|
|
135
|
+
string and a `params` dict. Discover actions with `action='help'` — the
|
|
136
|
+
default when `action` is omitted:
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{ "action": "help" }
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Actions at a glance
|
|
143
|
+
|
|
144
|
+
| action | purpose |
|
|
145
|
+
|-----------------------|--------------------------------------------------|
|
|
146
|
+
| `help` | list all actions and their signatures |
|
|
147
|
+
| `list_accounts` | see configured accounts (no secrets returned) |
|
|
148
|
+
| `list_folders` | list IMAP folders (UTF-7 decoded) |
|
|
149
|
+
| `list_recent` | last N messages of a folder |
|
|
150
|
+
| `search` | IMAP search by FROM/TO/SUBJECT/BODY/TEXT |
|
|
151
|
+
| `read` | read a message by UID (optionally with attachments) |
|
|
152
|
+
| `download_attachments`| fetch attachments as base64 |
|
|
153
|
+
| `send` | send an email (text/HTML/attachments) |
|
|
154
|
+
| `save_draft` | append a draft to the account's Drafts folder |
|
|
155
|
+
| `send_invite` | send an ICS calendar invite |
|
|
156
|
+
|
|
157
|
+
### Send an email with an attachment
|
|
158
|
+
|
|
159
|
+
```json
|
|
160
|
+
{
|
|
161
|
+
"action": "send",
|
|
162
|
+
"params": {
|
|
163
|
+
"to": "someone@example.com",
|
|
164
|
+
"subject": "Report",
|
|
165
|
+
"text": "See attached.",
|
|
166
|
+
"html": "<p>See <b>attached</b>.</p>",
|
|
167
|
+
"attachments": [
|
|
168
|
+
{"path": "/tmp/report.pdf"},
|
|
169
|
+
{"name": "note.txt", "content": "hi from inline"}
|
|
170
|
+
]
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Attachment shapes accepted:
|
|
176
|
+
- `{"path": "/local/file.pdf", "name": "renamed.pdf"?}` — read from disk
|
|
177
|
+
- `{"name": "x.bin", "content_base64": "..."}` — inline base64
|
|
178
|
+
- `{"name": "x.txt", "content": "hello", "mime": "text/plain"?}` — inline text
|
|
179
|
+
|
|
180
|
+
### Read a message and its attachments
|
|
181
|
+
|
|
182
|
+
```json
|
|
183
|
+
{
|
|
184
|
+
"action": "read",
|
|
185
|
+
"params": {"uid": "42", "folder": "INBOX", "include_attachments": true}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Download only specific attachments
|
|
190
|
+
|
|
191
|
+
```json
|
|
192
|
+
{
|
|
193
|
+
"action": "download_attachments",
|
|
194
|
+
"params": {"uid": "42", "filenames": ["report.pdf"]}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Send a calendar invite
|
|
199
|
+
|
|
200
|
+
```json
|
|
201
|
+
{
|
|
202
|
+
"action": "send_invite",
|
|
203
|
+
"params": {
|
|
204
|
+
"to": "guest@example.com",
|
|
205
|
+
"subject": "Kickoff",
|
|
206
|
+
"start": "2026-07-25 09:00",
|
|
207
|
+
"end": "2026-07-25 10:00",
|
|
208
|
+
"location": "Zoom link here",
|
|
209
|
+
"description": "quarter review"
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Datetimes accept ISO 8601 (`2026-07-25T09:00:00-06:00`) or
|
|
215
|
+
`YYYY-MM-DD HH:MM`. Naive times are assumed UTC.
|
|
216
|
+
|
|
217
|
+
### Save a draft
|
|
218
|
+
|
|
219
|
+
```json
|
|
220
|
+
{"action": "save_draft", "params": {"subject": "todo", "text": "..."}}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Search
|
|
224
|
+
|
|
225
|
+
```json
|
|
226
|
+
{"action": "search", "params": {"query": "invoice", "field": "SUBJECT"}}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Pick an account (multi-account setup)
|
|
230
|
+
|
|
231
|
+
Any action accepts an optional `account` param; omit it to use the first
|
|
232
|
+
configured account:
|
|
233
|
+
|
|
234
|
+
```json
|
|
235
|
+
{"action": "send", "params": {"account": "work", "to": "...", "text": "..."}}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Send gate (recommended when an agent has this tool)
|
|
239
|
+
|
|
240
|
+
Prevent accidental sends by requiring a shared secret:
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
export EMAIL_SEND_CONFIRMATION_CODE=please-send
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Every `send` / `send_invite` call must include `confirm: "please-send"` in
|
|
247
|
+
params, or the server refuses.
|
|
248
|
+
|
|
249
|
+
## Security notes
|
|
250
|
+
|
|
251
|
+
* `.env` is git-ignored. Never commit it.
|
|
252
|
+
* Prefer a **dedicated mailbox** (e.g. `mcp@yourdomain.com`) with a small quota.
|
|
253
|
+
* If your provider supports **app passwords** (Gmail, Fastmail, iCloud), use one
|
|
254
|
+
instead of your primary password.
|
|
255
|
+
* MCP env vars end up in `~/.claude.json` on your machine — treat that file
|
|
256
|
+
like a keychain.
|
|
257
|
+
|
|
258
|
+
## License
|
|
259
|
+
|
|
260
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# cpanel-mail-mcp
|
|
2
|
+
|
|
3
|
+
MCP server for IMAP/SMTP email accounts — works with cPanel, Gmail
|
|
4
|
+
(app passwords), Outlook, Fastmail, iCloud, or any provider that speaks
|
|
5
|
+
plain IMAP + SMTP.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
* **Multi-account** — manage multiple email accounts from different providers
|
|
10
|
+
* **Read, search, list** — full IMAP support with folder browsing
|
|
11
|
+
* **Send emails** — plain text, HTML, or both (multipart/alternative)
|
|
12
|
+
* **Attachments** — send via file path or base64-encoded inline data
|
|
13
|
+
* **Download attachments** — extract attachments from received emails as base64
|
|
14
|
+
* **Calendar invites** — send proper ICS invitations with Accept/Decline buttons
|
|
15
|
+
* **Save to Sent** — automatically saves sent emails to the Sent folder via IMAP
|
|
16
|
+
* **Optional send gate** — configurable confirmation code to prevent accidental sends
|
|
17
|
+
* **International folders** — handles UTF-7 encoded folder names (German, etc.)
|
|
18
|
+
* **Compact MCP surface** — one `email` tool with lazy action discovery to reduce
|
|
19
|
+
client context use
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
### With `uvx` (recommended, no venv setup)
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
claude mcp add cpanel-mail \
|
|
27
|
+
-e CPANEL_USER=you@example.com \
|
|
28
|
+
-e CPANEL_PASS='your_password' \
|
|
29
|
+
-e CPANEL_SMTP_HOST=mail.example.com \
|
|
30
|
+
-e CPANEL_IMAP_HOST=mail.example.com \
|
|
31
|
+
-- uvx cpanel-mail-mcp
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### With `pipx`
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pipx install cpanel-mail-mcp
|
|
38
|
+
claude mcp add cpanel-mail \
|
|
39
|
+
-e CPANEL_USER=you@example.com -e CPANEL_PASS='...' \
|
|
40
|
+
-e CPANEL_SMTP_HOST=mail.example.com -e CPANEL_IMAP_HOST=mail.example.com \
|
|
41
|
+
-- cpanel-mail-mcp
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Development install (from a git checkout)
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
git clone https://github.com/rsauceda/cpanel-mail-mcp
|
|
48
|
+
cd cpanel-mail-mcp
|
|
49
|
+
python -m venv .venv && source .venv/bin/activate
|
|
50
|
+
pip install -e .
|
|
51
|
+
cp .env.example .env # edit
|
|
52
|
+
cpanel-mail-mcp # runs the stdio server
|
|
53
|
+
# or: python -m cpanel_mail_mcp
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Configuration
|
|
57
|
+
|
|
58
|
+
Three ways, in priority order:
|
|
59
|
+
|
|
60
|
+
### 1. `EMAIL_ACCOUNTS_JSON` (best for multi-account)
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
export EMAIL_ACCOUNTS_JSON='[
|
|
64
|
+
{"name":"work","user":"me@work.com","password":"...",
|
|
65
|
+
"smtp_host":"mail.work.com","imap_host":"mail.work.com",
|
|
66
|
+
"sent_folder":"INBOX.Sent"},
|
|
67
|
+
{"name":"gmail","user":"me@gmail.com","password":"app_pass",
|
|
68
|
+
"smtp_host":"smtp.gmail.com","smtp_port":587,
|
|
69
|
+
"imap_host":"imap.gmail.com","sent_folder":"[Gmail]/Sent Mail"}
|
|
70
|
+
]'
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 2. `EMAIL_ACCOUNTS_FILE`
|
|
74
|
+
|
|
75
|
+
Point to a JSON file with the same shape.
|
|
76
|
+
|
|
77
|
+
### 3. Legacy single-account (`CPANEL_*`)
|
|
78
|
+
|
|
79
|
+
Backwards compatible with 0.1.x installs. See [`.env.example`](.env.example).
|
|
80
|
+
|
|
81
|
+
### Account fields
|
|
82
|
+
|
|
83
|
+
| field | required | default |
|
|
84
|
+
|-----------------|----------|-----------|
|
|
85
|
+
| `name` | no | `user` |
|
|
86
|
+
| `user` | **yes** | — |
|
|
87
|
+
| `password` | **yes** | — |
|
|
88
|
+
| `smtp_host` | **yes** | — |
|
|
89
|
+
| `smtp_port` | no | `465` |
|
|
90
|
+
| `imap_host` | **yes** | — |
|
|
91
|
+
| `imap_port` | no | `993` |
|
|
92
|
+
| `sent_folder` | no | `Sent` |
|
|
93
|
+
| `drafts_folder` | no | `Drafts` |
|
|
94
|
+
| `save_to_sent` | no | `true` |
|
|
95
|
+
| `from_name` | no | — |
|
|
96
|
+
|
|
97
|
+
### Loading a dev `.env`
|
|
98
|
+
|
|
99
|
+
The server auto-loads `.env` from the current working directory. Point it
|
|
100
|
+
elsewhere with:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
export EMAIL_ENV_FILE=/absolute/path/to/.env
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Usage — the `email` tool
|
|
107
|
+
|
|
108
|
+
The server exposes **one** MCP tool named `email`. Call it with an `action`
|
|
109
|
+
string and a `params` dict. Discover actions with `action='help'` — the
|
|
110
|
+
default when `action` is omitted:
|
|
111
|
+
|
|
112
|
+
```json
|
|
113
|
+
{ "action": "help" }
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Actions at a glance
|
|
117
|
+
|
|
118
|
+
| action | purpose |
|
|
119
|
+
|-----------------------|--------------------------------------------------|
|
|
120
|
+
| `help` | list all actions and their signatures |
|
|
121
|
+
| `list_accounts` | see configured accounts (no secrets returned) |
|
|
122
|
+
| `list_folders` | list IMAP folders (UTF-7 decoded) |
|
|
123
|
+
| `list_recent` | last N messages of a folder |
|
|
124
|
+
| `search` | IMAP search by FROM/TO/SUBJECT/BODY/TEXT |
|
|
125
|
+
| `read` | read a message by UID (optionally with attachments) |
|
|
126
|
+
| `download_attachments`| fetch attachments as base64 |
|
|
127
|
+
| `send` | send an email (text/HTML/attachments) |
|
|
128
|
+
| `save_draft` | append a draft to the account's Drafts folder |
|
|
129
|
+
| `send_invite` | send an ICS calendar invite |
|
|
130
|
+
|
|
131
|
+
### Send an email with an attachment
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"action": "send",
|
|
136
|
+
"params": {
|
|
137
|
+
"to": "someone@example.com",
|
|
138
|
+
"subject": "Report",
|
|
139
|
+
"text": "See attached.",
|
|
140
|
+
"html": "<p>See <b>attached</b>.</p>",
|
|
141
|
+
"attachments": [
|
|
142
|
+
{"path": "/tmp/report.pdf"},
|
|
143
|
+
{"name": "note.txt", "content": "hi from inline"}
|
|
144
|
+
]
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Attachment shapes accepted:
|
|
150
|
+
- `{"path": "/local/file.pdf", "name": "renamed.pdf"?}` — read from disk
|
|
151
|
+
- `{"name": "x.bin", "content_base64": "..."}` — inline base64
|
|
152
|
+
- `{"name": "x.txt", "content": "hello", "mime": "text/plain"?}` — inline text
|
|
153
|
+
|
|
154
|
+
### Read a message and its attachments
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
{
|
|
158
|
+
"action": "read",
|
|
159
|
+
"params": {"uid": "42", "folder": "INBOX", "include_attachments": true}
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Download only specific attachments
|
|
164
|
+
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"action": "download_attachments",
|
|
168
|
+
"params": {"uid": "42", "filenames": ["report.pdf"]}
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Send a calendar invite
|
|
173
|
+
|
|
174
|
+
```json
|
|
175
|
+
{
|
|
176
|
+
"action": "send_invite",
|
|
177
|
+
"params": {
|
|
178
|
+
"to": "guest@example.com",
|
|
179
|
+
"subject": "Kickoff",
|
|
180
|
+
"start": "2026-07-25 09:00",
|
|
181
|
+
"end": "2026-07-25 10:00",
|
|
182
|
+
"location": "Zoom link here",
|
|
183
|
+
"description": "quarter review"
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Datetimes accept ISO 8601 (`2026-07-25T09:00:00-06:00`) or
|
|
189
|
+
`YYYY-MM-DD HH:MM`. Naive times are assumed UTC.
|
|
190
|
+
|
|
191
|
+
### Save a draft
|
|
192
|
+
|
|
193
|
+
```json
|
|
194
|
+
{"action": "save_draft", "params": {"subject": "todo", "text": "..."}}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Search
|
|
198
|
+
|
|
199
|
+
```json
|
|
200
|
+
{"action": "search", "params": {"query": "invoice", "field": "SUBJECT"}}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Pick an account (multi-account setup)
|
|
204
|
+
|
|
205
|
+
Any action accepts an optional `account` param; omit it to use the first
|
|
206
|
+
configured account:
|
|
207
|
+
|
|
208
|
+
```json
|
|
209
|
+
{"action": "send", "params": {"account": "work", "to": "...", "text": "..."}}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Send gate (recommended when an agent has this tool)
|
|
213
|
+
|
|
214
|
+
Prevent accidental sends by requiring a shared secret:
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
export EMAIL_SEND_CONFIRMATION_CODE=please-send
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Every `send` / `send_invite` call must include `confirm: "please-send"` in
|
|
221
|
+
params, or the server refuses.
|
|
222
|
+
|
|
223
|
+
## Security notes
|
|
224
|
+
|
|
225
|
+
* `.env` is git-ignored. Never commit it.
|
|
226
|
+
* Prefer a **dedicated mailbox** (e.g. `mcp@yourdomain.com`) with a small quota.
|
|
227
|
+
* If your provider supports **app passwords** (Gmail, Fastmail, iCloud), use one
|
|
228
|
+
instead of your primary password.
|
|
229
|
+
* MCP env vars end up in `~/.claude.json` on your machine — treat that file
|
|
230
|
+
like a keychain.
|
|
231
|
+
|
|
232
|
+
## License
|
|
233
|
+
|
|
234
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "cpanel-mail-mcp"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "MCP server for IMAP/SMTP email — multi-account, attachments, ICS calendar invites, drafts, Save-to-Sent."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
authors = [{ name = "Rodrigo Sauceda", email = "rodrigo.sauceda@gmail.com" }]
|
|
9
|
+
keywords = ["mcp", "email", "imap", "smtp", "cpanel", "calendar", "ics", "attachments"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 4 - Beta",
|
|
12
|
+
"Intended Audience :: Developers",
|
|
13
|
+
"Operating System :: OS Independent",
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Programming Language :: Python :: 3.10",
|
|
16
|
+
"Programming Language :: Python :: 3.11",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
"Programming Language :: Python :: 3.13",
|
|
19
|
+
"Topic :: Communications :: Email",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"mcp[cli]>=1.2.0",
|
|
23
|
+
"python-dotenv>=1.0.0",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://github.com/rsauceda/cpanel-mail-mcp"
|
|
28
|
+
Repository = "https://github.com/rsauceda/cpanel-mail-mcp"
|
|
29
|
+
Issues = "https://github.com/rsauceda/cpanel-mail-mcp/issues"
|
|
30
|
+
|
|
31
|
+
[project.scripts]
|
|
32
|
+
cpanel-mail-mcp = "cpanel_mail_mcp.server:main"
|
|
33
|
+
|
|
34
|
+
[build-system]
|
|
35
|
+
requires = ["setuptools>=68"]
|
|
36
|
+
build-backend = "setuptools.build_meta"
|
|
37
|
+
|
|
38
|
+
[tool.setuptools]
|
|
39
|
+
package-dir = {"" = "src"}
|
|
40
|
+
|
|
41
|
+
[tool.setuptools.packages.find]
|
|
42
|
+
where = ["src"]
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Account discovery from environment.
|
|
2
|
+
|
|
3
|
+
Priority:
|
|
4
|
+
1. `EMAIL_ACCOUNTS_JSON` — JSON array of account dicts.
|
|
5
|
+
2. `EMAIL_ACCOUNTS_FILE` — path to a JSON file with the same array.
|
|
6
|
+
3. Legacy `CPANEL_USER`/`CPANEL_PASS`/... single account.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
|
+
class Account:
|
|
18
|
+
name: str
|
|
19
|
+
user: str
|
|
20
|
+
password: str
|
|
21
|
+
smtp_host: str
|
|
22
|
+
smtp_port: int
|
|
23
|
+
imap_host: str
|
|
24
|
+
imap_port: int
|
|
25
|
+
sent_folder: str
|
|
26
|
+
drafts_folder: str
|
|
27
|
+
save_to_sent: bool
|
|
28
|
+
from_name: str | None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _from_dict(d: dict) -> Account:
|
|
32
|
+
if not d.get("user") or not d.get("password"):
|
|
33
|
+
raise ValueError(f"account {d.get('name')!r} missing user/password")
|
|
34
|
+
host = d.get("host") or ""
|
|
35
|
+
return Account(
|
|
36
|
+
name=str(d.get("name") or d["user"]),
|
|
37
|
+
user=d["user"],
|
|
38
|
+
password=d["password"],
|
|
39
|
+
smtp_host=d.get("smtp_host") or host,
|
|
40
|
+
smtp_port=int(d.get("smtp_port", 465)),
|
|
41
|
+
imap_host=d.get("imap_host") or host,
|
|
42
|
+
imap_port=int(d.get("imap_port", 993)),
|
|
43
|
+
sent_folder=d.get("sent_folder", "Sent"),
|
|
44
|
+
drafts_folder=d.get("drafts_folder", "Drafts"),
|
|
45
|
+
save_to_sent=bool(d.get("save_to_sent", True)),
|
|
46
|
+
from_name=d.get("from_name"),
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _legacy() -> Account | None:
|
|
51
|
+
user = os.environ.get("CPANEL_USER")
|
|
52
|
+
pw = os.environ.get("CPANEL_PASS")
|
|
53
|
+
if not user or not pw:
|
|
54
|
+
return None
|
|
55
|
+
return _from_dict(
|
|
56
|
+
{
|
|
57
|
+
"name": os.environ.get("CPANEL_ACCOUNT_NAME", "default"),
|
|
58
|
+
"user": user,
|
|
59
|
+
"password": pw,
|
|
60
|
+
"smtp_host": os.environ.get("CPANEL_SMTP_HOST", ""),
|
|
61
|
+
"smtp_port": int(os.environ.get("CPANEL_SMTP_PORT", "465")),
|
|
62
|
+
"imap_host": os.environ.get("CPANEL_IMAP_HOST", ""),
|
|
63
|
+
"imap_port": int(os.environ.get("CPANEL_IMAP_PORT", "993")),
|
|
64
|
+
"sent_folder": os.environ.get("CPANEL_SENT_FOLDER", "INBOX.Sent"),
|
|
65
|
+
"drafts_folder": os.environ.get("CPANEL_DRAFTS_FOLDER", "INBOX.Drafts"),
|
|
66
|
+
"save_to_sent": os.environ.get("CPANEL_SAVE_TO_SENT", "true").lower() != "false",
|
|
67
|
+
"from_name": os.environ.get("CPANEL_FROM_NAME"),
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def load_accounts() -> dict[str, Account]:
|
|
73
|
+
accts: list[Account] = []
|
|
74
|
+
raw = os.environ.get("EMAIL_ACCOUNTS_JSON")
|
|
75
|
+
if raw:
|
|
76
|
+
accts.extend(_from_dict(d) for d in json.loads(raw))
|
|
77
|
+
else:
|
|
78
|
+
path = os.environ.get("EMAIL_ACCOUNTS_FILE")
|
|
79
|
+
if path:
|
|
80
|
+
accts.extend(_from_dict(d) for d in json.loads(Path(path).read_text()))
|
|
81
|
+
if not accts:
|
|
82
|
+
legacy = _legacy()
|
|
83
|
+
if legacy:
|
|
84
|
+
accts.append(legacy)
|
|
85
|
+
if not accts:
|
|
86
|
+
raise RuntimeError(
|
|
87
|
+
"No email accounts configured. Set EMAIL_ACCOUNTS_JSON, "
|
|
88
|
+
"EMAIL_ACCOUNTS_FILE, or the legacy CPANEL_USER/CPANEL_PASS/... env vars."
|
|
89
|
+
)
|
|
90
|
+
out: dict[str, Account] = {}
|
|
91
|
+
for a in accts:
|
|
92
|
+
if a.name in out:
|
|
93
|
+
raise ValueError(f"duplicate account name: {a.name!r}")
|
|
94
|
+
out[a.name] = a
|
|
95
|
+
return out
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def get_account(accounts: dict[str, Account], name: str | None) -> Account:
|
|
99
|
+
if not name:
|
|
100
|
+
return next(iter(accounts.values()))
|
|
101
|
+
if name in accounts:
|
|
102
|
+
return accounts[name]
|
|
103
|
+
raise ValueError(f"unknown account {name!r}. Available: {list(accounts)}")
|