@n24q02m/better-email-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.
- package/LICENSE +21 -0
- package/README.md +162 -0
- package/bin/cli.mjs +13 -0
- package/build/scripts/start-server.d.ts +6 -0
- package/build/scripts/start-server.d.ts.map +1 -0
- package/build/scripts/start-server.js +21 -0
- package/build/scripts/start-server.js.map +1 -0
- package/build/src/docs/attachments.md +48 -0
- package/build/src/docs/folders.md +45 -0
- package/build/src/docs/messages.md +92 -0
- package/build/src/docs/send.md +52 -0
- package/build/src/init-server.d.ts +40 -0
- package/build/src/init-server.d.ts.map +1 -0
- package/build/src/init-server.js +57 -0
- package/build/src/init-server.js.map +1 -0
- package/build/src/tools/composite/attachments.d.ts +17 -0
- package/build/src/tools/composite/attachments.d.ts.map +1 -0
- package/build/src/tools/composite/attachments.js +77 -0
- package/build/src/tools/composite/attachments.js.map +1 -0
- package/build/src/tools/composite/attachments.test.d.ts +2 -0
- package/build/src/tools/composite/attachments.test.d.ts.map +1 -0
- package/build/src/tools/composite/attachments.test.js +138 -0
- package/build/src/tools/composite/attachments.test.js.map +1 -0
- package/build/src/tools/composite/folders.d.ts +14 -0
- package/build/src/tools/composite/folders.d.ts.map +1 -0
- package/build/src/tools/composite/folders.js +57 -0
- package/build/src/tools/composite/folders.js.map +1 -0
- package/build/src/tools/composite/folders.test.d.ts +2 -0
- package/build/src/tools/composite/folders.test.d.ts.map +1 -0
- package/build/src/tools/composite/folders.test.js +61 -0
- package/build/src/tools/composite/folders.test.js.map +1 -0
- package/build/src/tools/composite/messages.d.ts +20 -0
- package/build/src/tools/composite/messages.d.ts.map +1 -0
- package/build/src/tools/composite/messages.js +244 -0
- package/build/src/tools/composite/messages.js.map +1 -0
- package/build/src/tools/composite/messages.test.d.ts +2 -0
- package/build/src/tools/composite/messages.test.d.ts.map +1 -0
- package/build/src/tools/composite/messages.test.js +217 -0
- package/build/src/tools/composite/messages.test.js.map +1 -0
- package/build/src/tools/composite/send.d.ts +21 -0
- package/build/src/tools/composite/send.d.ts.map +1 -0
- package/build/src/tools/composite/send.js +127 -0
- package/build/src/tools/composite/send.js.map +1 -0
- package/build/src/tools/composite/send.test.d.ts +2 -0
- package/build/src/tools/composite/send.test.d.ts.map +1 -0
- package/build/src/tools/composite/send.test.js +232 -0
- package/build/src/tools/composite/send.test.js.map +1 -0
- package/build/src/tools/helpers/config.d.ts +33 -0
- package/build/src/tools/helpers/config.d.ts.map +1 -0
- package/build/src/tools/helpers/config.js +167 -0
- package/build/src/tools/helpers/config.js.map +1 -0
- package/build/src/tools/helpers/config.test.d.ts +2 -0
- package/build/src/tools/helpers/config.test.d.ts.map +1 -0
- package/build/src/tools/helpers/config.test.js +141 -0
- package/build/src/tools/helpers/config.test.js.map +1 -0
- package/build/src/tools/helpers/errors.d.ts +34 -0
- package/build/src/tools/helpers/errors.d.ts.map +1 -0
- package/build/src/tools/helpers/errors.js +152 -0
- package/build/src/tools/helpers/errors.js.map +1 -0
- package/build/src/tools/helpers/errors.test.d.ts +2 -0
- package/build/src/tools/helpers/errors.test.d.ts.map +1 -0
- package/build/src/tools/helpers/errors.test.js +203 -0
- package/build/src/tools/helpers/errors.test.js.map +1 -0
- package/build/src/tools/helpers/html-utils.d.ts +10 -0
- package/build/src/tools/helpers/html-utils.d.ts.map +1 -0
- package/build/src/tools/helpers/html-utils.js +29 -0
- package/build/src/tools/helpers/html-utils.js.map +1 -0
- package/build/src/tools/helpers/html-utils.test.d.ts +2 -0
- package/build/src/tools/helpers/html-utils.test.d.ts.map +1 -0
- package/build/src/tools/helpers/html-utils.test.js +103 -0
- package/build/src/tools/helpers/html-utils.test.js.map +1 -0
- package/build/src/tools/helpers/imap-client.d.ts +90 -0
- package/build/src/tools/helpers/imap-client.d.ts.map +1 -0
- package/build/src/tools/helpers/imap-client.js +321 -0
- package/build/src/tools/helpers/imap-client.js.map +1 -0
- package/build/src/tools/helpers/imap-client.test.d.ts +2 -0
- package/build/src/tools/helpers/imap-client.test.d.ts.map +1 -0
- package/build/src/tools/helpers/imap-client.test.js +401 -0
- package/build/src/tools/helpers/imap-client.test.js.map +1 -0
- package/build/src/tools/helpers/smtp-client.d.ts +38 -0
- package/build/src/tools/helpers/smtp-client.d.ts.map +1 -0
- package/build/src/tools/helpers/smtp-client.js +124 -0
- package/build/src/tools/helpers/smtp-client.js.map +1 -0
- package/build/src/tools/helpers/smtp-client.test.d.ts +2 -0
- package/build/src/tools/helpers/smtp-client.test.d.ts.map +1 -0
- package/build/src/tools/helpers/smtp-client.test.js +210 -0
- package/build/src/tools/helpers/smtp-client.test.js.map +1 -0
- package/build/src/tools/registry.d.ts +11 -0
- package/build/src/tools/registry.d.ts.map +1 -0
- package/build/src/tools/registry.js +262 -0
- package/build/src/tools/registry.js.map +1 -0
- package/build/src/tools/registry.test.d.ts +2 -0
- package/build/src/tools/registry.test.d.ts.map +1 -0
- package/build/src/tools/registry.test.js +135 -0
- package/build/src/tools/registry.test.js.map +1 -0
- package/build/tsconfig.tsbuildinfo +1 -0
- package/package.json +81 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 n24q02m
|
|
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,162 @@
|
|
|
1
|
+
# Better Email MCP
|
|
2
|
+
|
|
3
|
+
**IMAP/SMTP MCP Server for Email - Optimized for AI Agents**
|
|
4
|
+
|
|
5
|
+
[](https://github.com/n24q02m/better-email-mcp/actions/workflows/ci.yml)
|
|
6
|
+
[](https://www.npmjs.com/package/@n24q02m/better-email-mcp)
|
|
7
|
+
[](https://hub.docker.com/r/n24q02m/better-email-mcp)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
|
|
10
|
+
## Why "Better"?
|
|
11
|
+
|
|
12
|
+
**5 composite tools** that provide full email operations (search, read, send, reply, forward, organize) across multiple accounts using IMAP/SMTP with App Passwords.
|
|
13
|
+
|
|
14
|
+
### Key Features
|
|
15
|
+
|
|
16
|
+
| Feature | Description |
|
|
17
|
+
|---------|-------------|
|
|
18
|
+
| **Multi-Account** | Manage 6+ email accounts simultaneously |
|
|
19
|
+
| **App Passwords** | No OAuth2 setup required - clone and run in 1 minute |
|
|
20
|
+
| **Auto-Discovery** | Gmail, Outlook, Yahoo, iCloud, Zoho, ProtonMail auto-configured |
|
|
21
|
+
| **Clean Text** | HTML stripped for LLM token savings |
|
|
22
|
+
| **Thread Support** | Reply/forward maintains In-Reply-To and References headers |
|
|
23
|
+
| **Composite Tools** | 5 tools with 15 actions (not 15+ separate endpoints) |
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
### Prerequisites
|
|
30
|
+
|
|
31
|
+
Create App Passwords (NOT your regular password):
|
|
32
|
+
- **Gmail**: Enable 2FA, then <https://myaccount.google.com/apppasswords>
|
|
33
|
+
- **Outlook**: Enable 2FA, then Security settings > App passwords
|
|
34
|
+
|
|
35
|
+
### Option 1: npx (Recommended)
|
|
36
|
+
|
|
37
|
+
```jsonc
|
|
38
|
+
{
|
|
39
|
+
"mcpServers": {
|
|
40
|
+
"better-email": {
|
|
41
|
+
"command": "npx",
|
|
42
|
+
"args": ["-y", "@n24q02m/better-email-mcp@latest"],
|
|
43
|
+
"env": {
|
|
44
|
+
"EMAIL_CREDENTIALS": "user@gmail.com:abcd-efgh-ijkl-mnop"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Option 2: Docker
|
|
52
|
+
|
|
53
|
+
```jsonc
|
|
54
|
+
{
|
|
55
|
+
"mcpServers": {
|
|
56
|
+
"better-email": {
|
|
57
|
+
"command": "docker",
|
|
58
|
+
"args": [
|
|
59
|
+
"run", "-i", "--rm",
|
|
60
|
+
"--name", "mcp-email",
|
|
61
|
+
"-e", "EMAIL_CREDENTIALS",
|
|
62
|
+
"n24q02m/better-email-mcp:latest"
|
|
63
|
+
],
|
|
64
|
+
"env": {
|
|
65
|
+
"EMAIL_CREDENTIALS": "user@gmail.com:abcd-efgh-ijkl-mnop"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Multiple Accounts
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
EMAIL_CREDENTIALS=user1@gmail.com:pass1,user2@outlook.com:pass2,user3@yahoo.com:pass3
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Custom IMAP Host
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
EMAIL_CREDENTIALS=user@custom.com:password:imap.custom.com
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Tools
|
|
87
|
+
|
|
88
|
+
| Tool | Actions |
|
|
89
|
+
|------|---------|
|
|
90
|
+
| `messages` | search, read, mark_read, mark_unread, flag, unflag, move, archive, trash |
|
|
91
|
+
| `folders` | list |
|
|
92
|
+
| `attachments` | list, download |
|
|
93
|
+
| `send` | new, reply, forward |
|
|
94
|
+
| `help` | Get full documentation for any tool |
|
|
95
|
+
|
|
96
|
+
### Search Query Language
|
|
97
|
+
|
|
98
|
+
| Query | Description |
|
|
99
|
+
|-------|-------------|
|
|
100
|
+
| `UNREAD` | Unread emails |
|
|
101
|
+
| `FLAGGED` | Starred emails |
|
|
102
|
+
| `SINCE 2024-01-01` | Emails after date |
|
|
103
|
+
| `FROM boss@company.com` | Emails from sender |
|
|
104
|
+
| `SUBJECT meeting` | Emails matching subject |
|
|
105
|
+
| `UNREAD SINCE 2024-06-01` | Compound filter |
|
|
106
|
+
| `UNREAD FROM boss@company.com` | Compound filter |
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Token Optimization
|
|
111
|
+
|
|
112
|
+
**Tiered descriptions** for minimal context usage:
|
|
113
|
+
|
|
114
|
+
| Tier | Purpose | When |
|
|
115
|
+
|------|---------|------|
|
|
116
|
+
| **Tier 1** | Compressed descriptions | Always loaded |
|
|
117
|
+
| **Tier 2** | Full docs via `help` tool | On-demand |
|
|
118
|
+
| **Tier 3** | MCP Resources | Supported clients |
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
{"name": "help", "tool_name": "messages"}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### MCP Resources (Tier 3)
|
|
125
|
+
|
|
126
|
+
| URI | Description |
|
|
127
|
+
|-----|-------------|
|
|
128
|
+
| `email://docs/messages` | Messages tool docs |
|
|
129
|
+
| `email://docs/folders` | Folders tool docs |
|
|
130
|
+
| `email://docs/attachments` | Attachments tool docs |
|
|
131
|
+
| `email://docs/send` | Send tool docs |
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Supported Providers
|
|
136
|
+
|
|
137
|
+
| Provider | Auto-Discovery | IMAP | SMTP |
|
|
138
|
+
|----------|---------------|------|------|
|
|
139
|
+
| Gmail | `imap.gmail.com:993` | TLS | TLS (465) |
|
|
140
|
+
| Outlook/Hotmail/Live | `outlook.office365.com:993` | TLS | STARTTLS (587) |
|
|
141
|
+
| Yahoo | `imap.mail.yahoo.com:993` | TLS | TLS (465) |
|
|
142
|
+
| iCloud/Me.com | `imap.mail.me.com:993` | TLS | STARTTLS (587) |
|
|
143
|
+
| Zoho | `imap.zoho.com:993` | TLS | TLS (465) |
|
|
144
|
+
| ProtonMail | `imap.protonmail.ch:993` | TLS | TLS (465) |
|
|
145
|
+
| Custom | Via `email:pass:imap.host` format | Configurable | Auto-derived |
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Build from Source
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
git clone https://github.com/n24q02m/better-email-mcp
|
|
153
|
+
cd better-email-mcp
|
|
154
|
+
mise run setup
|
|
155
|
+
pnpm build
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Requirements:** Node.js 24+, pnpm
|
|
159
|
+
|
|
160
|
+
## License
|
|
161
|
+
|
|
162
|
+
MIT - See [LICENSE](LICENSE)
|
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'module';const require = createRequire(import.meta.url);
|
|
3
|
+
import{readFileSync as Le}from"node:fs";import{dirname as xe,join as Pe}from"node:path";import{fileURLToPath as je}from"node:url";import{Server as Me}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as Ue}from"@modelcontextprotocol/sdk/server/stdio.js";var C={"gmail.com":{imap:{host:"imap.gmail.com",port:993,secure:!0},smtp:{host:"smtp.gmail.com",port:465,secure:!0}},"googlemail.com":{imap:{host:"imap.gmail.com",port:993,secure:!0},smtp:{host:"smtp.gmail.com",port:465,secure:!0}},"outlook.com":{imap:{host:"outlook.office365.com",port:993,secure:!0},smtp:{host:"smtp.office365.com",port:587,secure:!1}},"hotmail.com":{imap:{host:"outlook.office365.com",port:993,secure:!0},smtp:{host:"smtp.office365.com",port:587,secure:!1}},"live.com":{imap:{host:"outlook.office365.com",port:993,secure:!0},smtp:{host:"smtp.office365.com",port:587,secure:!1}},"yahoo.com":{imap:{host:"imap.mail.yahoo.com",port:993,secure:!0},smtp:{host:"smtp.mail.yahoo.com",port:465,secure:!0}},"icloud.com":{imap:{host:"imap.mail.me.com",port:993,secure:!0},smtp:{host:"smtp.mail.me.com",port:587,secure:!1}},"me.com":{imap:{host:"imap.mail.me.com",port:993,secure:!0},smtp:{host:"smtp.mail.me.com",port:587,secure:!1}},"zoho.com":{imap:{host:"imap.zoho.com",port:993,secure:!0},smtp:{host:"smtp.zoho.com",port:465,secure:!0}},"protonmail.com":{imap:{host:"imap.protonmail.ch",port:993,secure:!0},smtp:{host:"smtp.protonmail.ch",port:465,secure:!0}}};function ee(t){let e=t.split("@")[1]?.toLowerCase();if(!e)return null;if(C[e])return C[e];for(let[o,n]of Object.entries(C))if(e.endsWith(`.${o}`))return n;return null}function te(t){return t.replace(/[@.]/g,"_").toLowerCase()}function re(t){if(!t||t.trim()==="")return[];let e=[],o=t.split(",");for(let n of o){let r=n.trim();if(!r)continue;let s=r.split(":");if(s.length<2){console.error(`Skipping invalid credential entry (expected email:password): ${r.substring(0,20)}...`);continue}let i=s[0].trim(),c,l;if(s.length===2)c=s[1];else if(s.length===3){let u=s[2];u.includes(".")?(c=s[1],l=u):c=`${s[1]}:${s[2]}`}else{let u=s[s.length-1];u.includes(".")?(c=s.slice(1,-1).join(":"),l=u):c=s.slice(1).join(":")}let m,f;if(l)m={host:l,port:993,secure:!0},f={host:l.replace("imap.","smtp."),port:587,secure:!1};else{let u=ee(i);if(!u){console.error(`Cannot auto-discover settings for ${i}. Use format: email:password:imap.server.com`);continue}m=u.imap,f=u.smtp}e.push({id:te(i),email:i,password:c,imap:m,smtp:f})}return e}function v(){let t=process.env.EMAIL_CREDENTIALS;return t?re(t):[]}import{readFileSync as X}from"node:fs";import{dirname as Re,join as E}from"node:path";import{fileURLToPath as Oe}from"node:url";import{CallToolRequestSchema as Se,ListResourcesRequestSchema as Te,ListToolsRequestSchema as Ne,ReadResourceRequestSchema as ve}from"@modelcontextprotocol/sdk/types.js";var a=class extends Error{constructor(o,n,r,s){super(o);this.code=n;this.suggestion=r;this.details=s;this.name="EmailMCPError"}toJSON(){return{error:this.name,code:this.code,message:this.message,suggestion:this.suggestion,details:this.details}}};function D(t){if(!t||typeof t!="object")return t;let e={message:t.message,name:t.name,code:t.code};return t.status&&(e.status=t.status),t.responseCode&&(e.responseCode=t.responseCode),e}function I(t){let e=t.message||"Unknown error occurred";return e.includes("Invalid credentials")||e.includes("AUTHENTICATIONFAILED")||t.authenticationFailed?new a("Email authentication failed","AUTH_FAILED","Check that your email and App Password are correct. For Gmail: enable 2FA then create an App Password at https://myaccount.google.com/apppasswords. For Outlook: enable 2FA then create an App Password in security settings."):e.includes("ECONNREFUSED")||e.includes("ENOTFOUND")||e.includes("ETIMEDOUT")?new a("Cannot connect to email server","CONNECTION_ERROR","Check your internet connection and verify the email server address is correct."):e.includes("CERT")||e.includes("SSL")||e.includes("TLS")?new a("TLS/SSL connection error","TLS_ERROR","The email server certificate could not be verified. Check the server address and port."):e.includes("Mailbox not found")||e.includes("NO [NONEXISTENT]")?new a("Mailbox/folder not found","FOLDER_NOT_FOUND","Check the folder name. Use the folders tool to list available folders."):t.responseCode?oe(t):e.includes("EMAIL_CREDENTIALS")?new a("EMAIL_CREDENTIALS environment variable is required","CONFIG_ERROR","Set EMAIL_CREDENTIALS in format: email1:password1,email2:password2"):new a(e,"UNKNOWN_ERROR","Please check your request and try again",D(t))}function oe(t){let e=t.responseCode;switch(e){case 535:return new a("SMTP authentication failed","SMTP_AUTH_FAILED","Check your email and App Password for the sending account.");case 550:return new a("Recipient address rejected","RECIPIENT_REJECTED","Check the recipient email address is correct and exists.");case 552:case 554:return new a("Message rejected by server","MESSAGE_REJECTED","The email was rejected. It may be too large or flagged as spam.");default:return new a(t.message||`SMTP error ${e}`,`SMTP_${e}`,"Check the SMTP error code and try again.",D(t))}}function L(t){let e=`Error: ${t.message}`;return t.suggestion&&(e+=`
|
|
4
|
+
|
|
5
|
+
Suggestion: ${t.suggestion}`),t.details&&(e+=`
|
|
6
|
+
|
|
7
|
+
Details: ${JSON.stringify(t.details,null,2)}`),e}function p(t){return async(...e)=>{try{return await t(...e)}catch(o){throw I(o)}}}import{ImapFlow as se}from"imapflow";import{simpleParser as P}from"mailparser";import{convert as ne}from"html-to-text";function x(t){return t?ne(t,{wordwrap:!1,preserveNewlines:!0,selectors:[{selector:"style",format:"skip"},{selector:"script",format:"skip"},{selector:"img",format:"skip"},{selector:"a",options:{hideLinkHrefIfSameAsText:!0,ignoreHref:!1}},{selector:"table",format:"dataTable"}]}).trim():""}function ae(t){return new se({host:t.imap.host,port:t.imap.port,secure:t.imap.secure,auth:{user:t.email,pass:t.password},logger:!1})}async function h(t,e){let o=ae(t);try{return await o.connect(),await e(o)}finally{try{await o.logout()}catch{}}}function ie(t){let e=t.toUpperCase().trim();if(e==="UNREAD"||e==="UNSEEN")return{seen:!1};if(e==="READ"||e==="SEEN")return{seen:!0};if(e==="FLAGGED"||e==="STARRED")return{flagged:!0};if(e==="UNFLAGGED"||e==="UNSTARRED")return{flagged:!1};if(e==="ALL"||e==="*")return{};let o=t.match(/^SINCE\s+(\d{4}-\d{2}-\d{2})$/i);if(o)return{since:new Date(o[1])};let n=t.match(/^FROM\s+(.+)$/i);if(n)return{from:n[1]};let r=t.match(/^SUBJECT\s+(.+)$/i);if(r)return{subject:r[1]};let s=t.match(/^UNREAD\s+SINCE\s+(\d{4}-\d{2}-\d{2})$/i);if(s)return{seen:!1,since:new Date(s[1])};let i=t.match(/^UNREAD\s+FROM\s+(.+)$/i);return i?{seen:!1,from:i[1]}:{subject:t}}function ce(t,e=200){let o=t.replace(/\s+/g," ").trim();return o.length<=e?o:`${o.substring(0,e)}...`}function A(t){return t?typeof t=="string"?t:t.text?t.text:Array.isArray(t.value)?t.value.map(e=>e.name?`${e.name} <${e.address}>`:e.address).join(", "):"":""}async function j(t,e,o,n){let r=[],s=ie(e);for(let i of t)try{let c=await h(i,async l=>{let m=await l.getMailboxLock(o);try{let f=[],u=0;for await(let d of l.fetch(s,{uid:!0,flags:!0,envelope:!0,bodyStructure:!0,source:{start:0,maxLength:500}})){if(u>=n)break;let Q=d.source?ce(d.source.toString("utf-8")):"";f.push({account_id:i.id,account_email:i.email,uid:d.uid,message_id:d.envelope?.messageId,subject:d.envelope?.subject||"(No subject)",from:d.envelope?.from?.[0]?`${d.envelope.from[0].name||""} <${d.envelope.from[0].address||""}>`.trim():"",to:d.envelope?.to?.map(Z=>Z.address).join(", ")||"",date:d.envelope?.date?.toISOString()||"",flags:Array.from(d.flags||[]),snippet:Q}),u++}return f}finally{m.release()}});r.push(...c)}catch(c){r.push({account_id:i.id,account_email:i.email,uid:0,subject:`[ERROR] ${c.message}`,from:"",to:"",date:"",flags:[],snippet:`Failed to search ${i.email}: ${c.message}`})}return r}async function y(t,e,o){return h(t,async n=>{let r=await n.getMailboxLock(o);try{let s=await n.fetchOne(`${e}`,{uid:!0,flags:!0,source:!0});if(!s||!s.source)throw new a(`Email UID ${e} not found in ${o}`,"NOT_FOUND","Check the UID and folder");let i=s,c=await P(i.source),l=c.text||(c.html?x(c.html):"(Empty body)");return{account_id:t.id,account_email:t.email,uid:i.uid,message_id:c.messageId,in_reply_to:c.inReplyTo,references:Array.isArray(c.references)?c.references.join(" "):c.references,subject:c.subject||"(No subject)",from:A(c.from),to:A(c.to),cc:A(c.cc),bcc:A(c.bcc),date:c.date?.toISOString()||"",flags:Array.from(i.flags||[]),body_text:l,attachments:(c.attachments||[]).map(m=>({filename:m.filename||"unnamed",content_type:m.contentType||"application/octet-stream",size:m.size||0,content_id:m.contentId}))}}finally{r.release()}})}async function w(t,e,o,n,r){return h(t,async s=>{let i=await s.getMailboxLock(o);try{let c=e.join(",");return r==="add"?await s.messageFlagsAdd({uid:c},n):await s.messageFlagsRemove({uid:c},n),{success:!0,modified:e.length}}finally{i.release()}})}async function _(t,e,o,n){return h(t,async r=>{let s=await r.getMailboxLock(o);try{let i=e.join(",");return await r.messageMove({uid:i},n),{success:!0,moved:e.length}}finally{s.release()}})}async function M(t,e,o){return h(t,async n=>{let r=await n.getMailboxLock(o);try{let s=e.join(",");return await n.messageDelete({uid:s}),{success:!0,trashed:e.length}}finally{r.release()}})}async function b(t){return h(t,async e=>(await e.list()).map(n=>({name:n.name,path:n.path,flags:Array.from(n.flags||[]),delimiter:n.delimiter||"/",children:n.folders?Array.from(n.folders).map(([,r])=>({name:r.name,path:r.path,flags:Array.from(r.flags||[]),delimiter:r.delimiter||"/"})):void 0})))}async function U(t,e,o,n){return h(t,async r=>{let s=await r.getMailboxLock(o);try{let i=await r.fetchOne(`${e}`,{uid:!0,source:!0});if(!i||!i.source)throw new a(`Email UID ${e} not found`,"NOT_FOUND","Check the UID and folder");let c=await P(i.source),l=c.attachments?.find(m=>m.filename?.toLowerCase()===n.toLowerCase());if(!l)throw new a(`Attachment "${n}" not found`,"ATTACHMENT_NOT_FOUND",`Available: ${c.attachments?.map(m=>m.filename).join(", ")||"none"}`);return{filename:l.filename||"unnamed",content_type:l.contentType||"application/octet-stream",size:l.size||0,content_base64:l.content.toString("base64")}}finally{s.release()}})}function k(t,e){let o=e.toLowerCase(),n=t.filter(r=>r.email.toLowerCase()===o||r.id===o||r.email.toLowerCase().includes(o));if(n.length===0)throw new a(`Account not found: ${e}`,"ACCOUNT_NOT_FOUND",`Available accounts: ${t.map(r=>r.email).join(", ")}`);if(n.length>1)throw new a("Multiple accounts matched. Specify the exact account email.","AMBIGUOUS_ACCOUNT",`Matched: ${n.map(r=>r.email).join(", ")}`);return n[0]}async function F(t,e){return p(async()=>{if(!e.account)throw new a("account is required for attachment operations","VALIDATION_ERROR","Provide the account email address");if(!e.uid)throw new a("uid is required for attachment operations","VALIDATION_ERROR","Provide the email UID from search/read");switch(e.action){case"list":return await le(t,e);case"download":return await me(t,e);default:throw new a(`Unknown action: ${e.action}`,"VALIDATION_ERROR","Supported actions: list, download")}})()}async function le(t,e){let o=k(t,e.account),n=e.folder||"INBOX",r=await y(o,e.uid,n);return{action:"list",account:o.email,uid:e.uid,folder:n,subject:r.subject,total:r.attachments.length,attachments:r.attachments}}async function me(t,e){if(!e.filename)throw new a("filename is required for download action","VALIDATION_ERROR","Use attachments list action first to see available filenames");let o=k(t,e.account),n=e.folder||"INBOX",r=await U(o,e.uid,n,e.filename);return{action:"download",account:o.email,uid:e.uid,folder:n,...r}}async function $(t,e){return p(async()=>{switch(e.action){case"list":return await ue(t,e);default:throw new a(`Unknown action: ${e.action}`,"VALIDATION_ERROR","Supported actions: list")}})()}async function ue(t,e){let o=t;if(e.account){let r=e.account.toLowerCase();if(o=t.filter(s=>s.email.toLowerCase()===r||s.id===r||s.email.toLowerCase().includes(r)),o.length===0)throw new a(`Account not found: ${e.account}`,"ACCOUNT_NOT_FOUND",`Available accounts: ${t.map(s=>s.email).join(", ")}`)}let n=[];for(let r of o)try{let s=await b(r);n.push({account_id:r.id,account_email:r.email,folders:s})}catch(s){n.push({account_id:r.id,account_email:r.email,error:s.message,folders:[]})}return{action:"list",total_accounts:n.length,accounts:n}}function q(t,e){if(!e)return t;let o=e.toLowerCase(),n=t.filter(r=>r.email.toLowerCase()===o||r.id===o||r.email.toLowerCase().includes(o));if(n.length===0)throw new a(`Account not found: ${e}`,"ACCOUNT_NOT_FOUND",`Available accounts: ${t.map(r=>r.email).join(", ")}`);return n}function g(t,e){let o=q(t,e);if(o.length>1)throw new a("Multiple accounts matched. Specify the exact account email.","AMBIGUOUS_ACCOUNT",`Matched: ${o.map(n=>n.email).join(", ")}`);return o[0]}async function H(t,e){return p(async()=>{switch(e.action){case"search":return await de(t,e);case"read":return await fe(t,e);case"mark_read":return await pe(t,e);case"mark_unread":return await ge(t,e);case"flag":return await he(t,e);case"unflag":return await ye(t,e);case"move":return await we(t,e);case"archive":return await Ae(t,e);case"trash":return await be(t,e);default:throw new a(`Unknown action: ${e.action}`,"VALIDATION_ERROR","Supported actions: search, read, mark_read, mark_unread, flag, unflag, move, archive, trash")}})()}async function de(t,e){let o=q(t,e.account),n=e.query||"UNSEEN",r=e.folder||"INBOX",s=e.limit||20,i=await j(o,n,r,s);return{action:"search",query:n,folder:r,total:i.length,accounts_searched:o.map(c=>c.email),messages:i}}async function fe(t,e){if(!e.uid)throw new a("uid is required for read action","VALIDATION_ERROR","Provide the email UID from search");let o=g(t,e.account),n=e.folder||"INBOX";return{action:"read",...await y(o,e.uid,n)}}async function pe(t,e){let o=e.uids||(e.uid?[e.uid]:[]);if(o.length===0)throw new a("uid or uids required","VALIDATION_ERROR","Provide at least one email UID");let n=g(t,e.account),r=e.folder||"INBOX",s=await w(n,o,r,["\\Seen"],"add");return{action:"mark_read",account:n.email,folder:r,...s}}async function ge(t,e){let o=e.uids||(e.uid?[e.uid]:[]);if(o.length===0)throw new a("uid or uids required","VALIDATION_ERROR","Provide at least one email UID");let n=g(t,e.account),r=e.folder||"INBOX",s=await w(n,o,r,["\\Seen"],"remove");return{action:"mark_unread",account:n.email,folder:r,...s}}async function he(t,e){let o=e.uids||(e.uid?[e.uid]:[]);if(o.length===0)throw new a("uid or uids required","VALIDATION_ERROR","Provide at least one email UID");let n=g(t,e.account),r=e.folder||"INBOX",s=await w(n,o,r,["\\Flagged"],"add");return{action:"flag",account:n.email,folder:r,...s}}async function ye(t,e){let o=e.uids||(e.uid?[e.uid]:[]);if(o.length===0)throw new a("uid or uids required","VALIDATION_ERROR","Provide at least one email UID");let n=g(t,e.account),r=e.folder||"INBOX",s=await w(n,o,r,["\\Flagged"],"remove");return{action:"unflag",account:n.email,folder:r,...s}}async function we(t,e){let o=e.uids||(e.uid?[e.uid]:[]);if(o.length===0)throw new a("uid or uids required","VALIDATION_ERROR","Provide at least one email UID");if(!e.destination)throw new a("destination is required for move action","VALIDATION_ERROR","Provide the target folder name. Use folders tool to list available folders.");let n=g(t,e.account),r=e.folder||"INBOX",s=await _(n,o,r,e.destination);return{action:"move",account:n.email,from_folder:r,to_folder:e.destination,...s}}async function Ae(t,e){let o=e.uids||(e.uid?[e.uid]:[]);if(o.length===0)throw new a("uid or uids required","VALIDATION_ERROR","Provide at least one email UID");let n=g(t,e.account),r=e.folder||"INBOX",s="[Gmail]/All Mail";(n.imap.host.includes("office365")||n.imap.host.includes("outlook")||n.imap.host.includes("yahoo"))&&(s="Archive");try{let l=(await b(n)).find(m=>m.path.toLowerCase().includes("archive")||m.path.toLowerCase().includes("all mail")||m.flags.some(f=>f.toLowerCase().includes("archive")||f.toLowerCase().includes("all")));l&&(s=l.path)}catch{}let i=await _(n,o,r,s);return{action:"archive",account:n.email,from_folder:r,archive_folder:s,...i}}async function be(t,e){let o=e.uids||(e.uid?[e.uid]:[]);if(o.length===0)throw new a("uid or uids required","VALIDATION_ERROR","Provide at least one email UID");let n=g(t,e.account),r=e.folder||"INBOX",s=await M(n,o,r);return{action:"trash",account:n.email,folder:r,...s}}import{createTransport as Ee}from"nodemailer";function R(t){return Ee({host:t.smtp.host,port:t.smtp.port,secure:t.smtp.secure,auth:{user:t.email,pass:t.password}})}function O(t){return t.split(`
|
|
8
|
+
`).map(e=>e.startsWith("# ")?`<h1>${e.substring(2)}</h1>`:e.startsWith("## ")?`<h2>${e.substring(3)}</h2>`:e.startsWith("### ")?`<h3>${e.substring(4)}</h3>`:e.startsWith("- ")?`<li>${e.substring(2)}</li>`:e.startsWith("**")&&e.endsWith("**")?`<b>${e.slice(2,-2)}</b>`:e.trim()===""?"<br>":`<p>${e}</p>`).join(`
|
|
9
|
+
`)}async function V(t,e){let o=R(t);try{return{success:!0,message_id:(await o.sendMail({from:t.email,to:e.to,cc:e.cc,bcc:e.bcc,subject:e.subject,text:e.body,html:O(e.body)})).messageId||""}}finally{o.close()}}async function B(t,e){if(!e.in_reply_to)throw new a("in_reply_to is required for reply","MISSING_PARAM","Use email_read to get the message_id of the email you want to reply to");let o=R(t);try{let n=e.subject.startsWith("Re:")?e.subject:`Re: ${e.subject}`;return{success:!0,message_id:(await o.sendMail({from:t.email,to:e.to,cc:e.cc,bcc:e.bcc,subject:n,text:e.body,html:O(e.body),inReplyTo:e.in_reply_to,references:e.references||e.in_reply_to})).messageId||""}}finally{o.close()}}async function G(t,e){let o=R(t);try{let n=e.subject.startsWith("Fwd:")?e.subject:`Fwd: ${e.subject}`,r=`${e.body}
|
|
10
|
+
|
|
11
|
+
---------- Forwarded message ----------
|
|
12
|
+
${e.original_body}`;return{success:!0,message_id:(await o.sendMail({from:t.email,to:e.to,cc:e.cc,bcc:e.bcc,subject:n,text:r,html:O(r)})).messageId||""}}finally{o.close()}}function S(t,e){let o=e.toLowerCase(),n=t.filter(r=>r.email.toLowerCase()===o||r.id===o||r.email.toLowerCase().includes(o));if(n.length===0)throw new a(`Account not found: ${e}`,"ACCOUNT_NOT_FOUND",`Available accounts: ${t.map(r=>r.email).join(", ")}`);if(n.length>1)throw new a("Multiple accounts matched. Specify the exact account email.","AMBIGUOUS_ACCOUNT",`Matched: ${n.map(r=>r.email).join(", ")}`);return n[0]}async function W(t,e){return p(async()=>{if(!e.account)throw new a("account is required for send operations","VALIDATION_ERROR","Provide the sender account email address");if(!e.to)throw new a("to is required","VALIDATION_ERROR","Provide recipient email address");if(!e.body)throw new a("body is required","VALIDATION_ERROR","Provide the email body text");switch(e.action){case"new":return await Ce(t,e);case"reply":return await Ie(t,e);case"forward":return await _e(t,e);default:throw new a(`Unknown action: ${e.action}`,"VALIDATION_ERROR","Supported actions: new, reply, forward")}})()}async function Ce(t,e){if(!e.subject)throw new a("subject is required for new email","VALIDATION_ERROR","Provide the email subject");let o=S(t,e.account),n=await V(o,{to:e.to,subject:e.subject,body:e.body,cc:e.cc,bcc:e.bcc});return{action:"new",from:o.email,to:e.to,subject:e.subject,...n}}async function Ie(t,e){if(!e.uid)throw new a("uid is required for reply action","VALIDATION_ERROR","Provide the UID of the email to reply to (from search/read)");let o=S(t,e.account),n=e.folder||"INBOX",r=await y(o,e.uid,n),s=await B(o,{to:e.to,subject:e.subject||r.subject,body:e.body,cc:e.cc,bcc:e.bcc,in_reply_to:r.message_id,references:r.references||r.message_id});return{action:"reply",from:o.email,to:e.to,subject:e.subject||`Re: ${r.subject}`,in_reply_to:r.message_id,...s}}async function _e(t,e){if(!e.uid)throw new a("uid is required for forward action","VALIDATION_ERROR","Provide the UID of the email to forward (from search/read)");let o=S(t,e.account),n=e.folder||"INBOX",r=await y(o,e.uid,n),s=await G(o,{to:e.to,subject:e.subject||r.subject,body:e.body,cc:e.cc,bcc:e.bcc,original_body:r.body_text});return{action:"forward",from:o.email,to:e.to,subject:e.subject||`Fwd: ${r.subject}`,...s}}var De=Oe(import.meta.url),T=Re(De),z=T.endsWith("bin")?E(T,"..","build","src","docs"):E(T,"..","docs"),N=[{uri:"email://docs/messages",name:"Messages Tool Docs",file:"messages.md"},{uri:"email://docs/folders",name:"Folders Tool Docs",file:"folders.md"},{uri:"email://docs/attachments",name:"Attachments Tool Docs",file:"attachments.md"},{uri:"email://docs/send",name:"Send Tool Docs",file:"send.md"}],J=[{name:"messages",description:"Email messages: search, read, mark_read, mark_unread, flag, unflag, move, archive, trash. Search across all accounts or filter by account. Query supports: UNREAD, FLAGGED, SINCE YYYY-MM-DD, FROM x, SUBJECT x.",annotations:{title:"Messages",readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1},inputSchema:{type:"object",properties:{action:{type:"string",enum:["search","read","mark_read","mark_unread","flag","unflag","move","archive","trash"],description:"Action to perform"},account:{type:"string",description:"Account email filter (optional, defaults to all for search)"},query:{type:"string",description:"Search query: UNREAD, FLAGGED, SINCE YYYY-MM-DD, FROM email, SUBJECT text, or combined (default: UNSEEN)"},folder:{type:"string",description:"Mailbox folder (default: INBOX)"},limit:{type:"number",description:"Max results for search (default: 20)"},uid:{type:"number",description:"Email UID (for read/modify single email)"},uids:{type:"array",items:{type:"number"},description:"Multiple UIDs for batch operations"},destination:{type:"string",description:"Target folder for move action"}},required:["action"]}},{name:"folders",description:"List mailbox folders for one or all email accounts. Returns folder names, paths, and flags.",annotations:{title:"Folders",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},inputSchema:{type:"object",properties:{action:{type:"string",enum:["list"],description:"Action to perform"},account:{type:"string",description:"Account email filter (optional, defaults to all)"}},required:["action"]}},{name:"attachments",description:"Email attachments: list, download. List shows all attachments for an email. Download returns base64-encoded content.",annotations:{title:"Attachments",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},inputSchema:{type:"object",properties:{action:{type:"string",enum:["list","download"],description:"Action to perform"},account:{type:"string",description:"Account email (required)"},uid:{type:"number",description:"Email UID (required)"},folder:{type:"string",description:"Mailbox folder (default: INBOX)"},filename:{type:"string",description:"Attachment filename (required for download)"}},required:["action","account","uid"]}},{name:"send",description:"Send emails: new, reply, forward. Reply maintains thread headers (In-Reply-To, References). Forward includes original body.",annotations:{title:"Send",readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!0},inputSchema:{type:"object",properties:{action:{type:"string",enum:["new","reply","forward"],description:"Action to perform"},account:{type:"string",description:"Sender account email (required)"},to:{type:"string",description:"Recipient email address (required)"},subject:{type:"string",description:"Email subject (required for new)"},body:{type:"string",description:"Email body text (required)"},cc:{type:"string",description:"CC recipients (comma-separated)"},bcc:{type:"string",description:"BCC recipients (comma-separated)"},uid:{type:"number",description:"Original email UID (required for reply/forward)"},folder:{type:"string",description:"Folder of original email (default: INBOX)"}},required:["action","account","to","body"]}},{name:"help",description:"Get full documentation for a tool. Use when compressed descriptions are insufficient.",annotations:{title:"Help",readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},inputSchema:{type:"object",properties:{tool_name:{type:"string",enum:["messages","folders","attachments","send"],description:"Tool to get documentation for"}},required:["tool_name"]}}];function Y(t,e){t.setRequestHandler(Ne,async()=>({tools:J})),t.setRequestHandler(Te,async()=>({resources:N.map(o=>({uri:o.uri,name:o.name,mimeType:"text/markdown"}))})),t.setRequestHandler(ve,async o=>{let{uri:n}=o.params,r=N.find(i=>i.uri===n);if(!r)throw new a(`Resource not found: ${n}`,"RESOURCE_NOT_FOUND",`Available: ${N.map(i=>i.uri).join(", ")}`);let s=X(E(z,r.file),"utf-8");return{contents:[{uri:n,mimeType:"text/markdown",text:s}]}}),t.setRequestHandler(Se,async o=>{let{name:n,arguments:r}=o.params;if(!r)return{content:[{type:"text",text:"Error: No arguments provided"}],isError:!0};try{let s;switch(n){case"messages":s=await H(e,r);break;case"folders":s=await $(e,r);break;case"attachments":s=await F(e,r);break;case"send":s=await W(e,r);break;case"help":{let i=r.tool_name,c=`${i}.md`;try{let l=X(E(z,c),"utf-8");s={tool:i,documentation:l}}catch{throw new a(`Documentation not found for: ${i}`,"DOC_NOT_FOUND","Check tool_name")}break}default:throw new a(`Unknown tool: ${n}`,"UNKNOWN_TOOL",`Available tools: ${J.map(i=>i.name).join(", ")}`)}return{content:[{type:"text",text:JSON.stringify(s,null,2)}]}}catch(s){let i=s instanceof a?s:I(s);return{content:[{type:"text",text:L(i)}],isError:!0}}})}var ke=je(import.meta.url),Fe=xe(ke);function $e(){try{let t=Pe(Fe,"..","package.json");return JSON.parse(Le(t,"utf-8")).version??"0.0.0"}catch{return"0.0.0"}}async function K(){let t=v();t.length===0&&(console.error("EMAIL_CREDENTIALS environment variable is required"),console.error("Format: email1:password1,email2:password2"),console.error(""),console.error("Examples:"),console.error(" EMAIL_CREDENTIALS=user@gmail.com:abcd-efgh-ijkl-mnop"),console.error(" EMAIL_CREDENTIALS=user1@gmail.com:pass1,user2@outlook.com:pass2"),console.error(""),console.error("For Gmail: Enable 2FA, then create App Password at https://myaccount.google.com/apppasswords"),console.error("For Outlook: Enable 2FA, then create App Password in security settings"),process.exit(1)),console.error(`Loaded ${t.length} email account(s): ${t.map(n=>n.email).join(", ")}`);let e=new Me({name:"@n24q02m/better-email-mcp",version:$e()},{capabilities:{tools:{},resources:{}}});Y(e,t);let o=new Ue;return await e.connect(o),e}async function qe(){try{await K(),process.on("SIGINT",()=>{console.error(`
|
|
13
|
+
Shutting down Better Email MCP Server`),process.exit(0)})}catch(t){console.error("Failed to start server:",t),process.exit(1)}}qe();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"start-server.d.ts","sourceRoot":"","sources":["../../scripts/start-server.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Better Email MCP Server Starter
|
|
3
|
+
* Simplified to use composite tools only
|
|
4
|
+
*/
|
|
5
|
+
import { initServer } from '../src/init-server.js';
|
|
6
|
+
async function startServer() {
|
|
7
|
+
try {
|
|
8
|
+
await initServer();
|
|
9
|
+
// Keep process running
|
|
10
|
+
process.on('SIGINT', () => {
|
|
11
|
+
console.error('\nShutting down Better Email MCP Server');
|
|
12
|
+
process.exit(0);
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
console.error('Failed to start server:', error);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
startServer();
|
|
21
|
+
//# sourceMappingURL=start-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"start-server.js","sourceRoot":"","sources":["../../scripts/start-server.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAElD,KAAK,UAAU,WAAW;IACxB,IAAI,CAAC;QACH,MAAM,UAAU,EAAE,CAAA;QAElB,uBAAuB;QACvB,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;YACxB,OAAO,CAAC,KAAK,CAAC,yCAAyC,CAAC,CAAA;YACxD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACjB,CAAC,CAAC,CAAA;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,KAAK,CAAC,CAAA;QAC/C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;AACH,CAAC;AAED,WAAW,EAAE,CAAA"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Attachments Tool - Full Documentation
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
List and download email attachments.
|
|
5
|
+
|
|
6
|
+
## Important
|
|
7
|
+
- **list** returns metadata only (filename, content_type, size)
|
|
8
|
+
- **download** returns base64-encoded content
|
|
9
|
+
- Large attachments may consume significant tokens - check size before downloading
|
|
10
|
+
- `account` and `uid` are required for all actions
|
|
11
|
+
|
|
12
|
+
## Actions
|
|
13
|
+
|
|
14
|
+
### list
|
|
15
|
+
List all attachments for an email.
|
|
16
|
+
```json
|
|
17
|
+
{"action": "list", "account": "user@gmail.com", "uid": 12345}
|
|
18
|
+
```
|
|
19
|
+
```json
|
|
20
|
+
{"action": "list", "account": "user@gmail.com", "uid": 12345, "folder": "INBOX"}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### download
|
|
24
|
+
Download a specific attachment by filename. Returns base64-encoded content.
|
|
25
|
+
```json
|
|
26
|
+
{"action": "download", "account": "user@gmail.com", "uid": 12345, "filename": "report.pdf"}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Parameters
|
|
30
|
+
- `action` - Action to perform (required): list, download
|
|
31
|
+
- `account` - Account email (required)
|
|
32
|
+
- `uid` - Email UID (required)
|
|
33
|
+
- `folder` - Mailbox folder (default: INBOX)
|
|
34
|
+
- `filename` - Attachment filename (required for download, case-insensitive)
|
|
35
|
+
|
|
36
|
+
## Response Fields
|
|
37
|
+
|
|
38
|
+
### list response
|
|
39
|
+
- `attachments[].filename` - Attachment filename
|
|
40
|
+
- `attachments[].content_type` - MIME type (e.g. application/pdf)
|
|
41
|
+
- `attachments[].size` - Size in bytes
|
|
42
|
+
- `attachments[].content_id` - Content-ID for inline attachments
|
|
43
|
+
|
|
44
|
+
### download response
|
|
45
|
+
- `filename` - Attachment filename
|
|
46
|
+
- `content_type` - MIME type
|
|
47
|
+
- `size` - Size in bytes
|
|
48
|
+
- `content_base64` - Base64-encoded file content
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Folders Tool - Full Documentation
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
List mailbox folders for one or all email accounts.
|
|
5
|
+
|
|
6
|
+
## Important
|
|
7
|
+
- Returns folder **names, paths, flags, and children**
|
|
8
|
+
- Gmail uses labels (e.g. `[Gmail]/All Mail`, `[Gmail]/Trash`)
|
|
9
|
+
- Outlook uses standard folders (Inbox, Sent, Drafts, Archive, Junk)
|
|
10
|
+
- Folder paths are case-sensitive
|
|
11
|
+
|
|
12
|
+
## Actions
|
|
13
|
+
|
|
14
|
+
### list
|
|
15
|
+
List all folders for all accounts or a specific account.
|
|
16
|
+
```json
|
|
17
|
+
{"action": "list"}
|
|
18
|
+
```
|
|
19
|
+
```json
|
|
20
|
+
{"action": "list", "account": "user@gmail.com"}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Parameters
|
|
24
|
+
- `action` - Action to perform (required): list
|
|
25
|
+
- `account` - Account email filter (optional, defaults to all)
|
|
26
|
+
|
|
27
|
+
## Common Folder Names
|
|
28
|
+
|
|
29
|
+
### Gmail
|
|
30
|
+
- `INBOX`
|
|
31
|
+
- `[Gmail]/All Mail`
|
|
32
|
+
- `[Gmail]/Drafts`
|
|
33
|
+
- `[Gmail]/Important`
|
|
34
|
+
- `[Gmail]/Sent Mail`
|
|
35
|
+
- `[Gmail]/Spam`
|
|
36
|
+
- `[Gmail]/Starred`
|
|
37
|
+
- `[Gmail]/Trash`
|
|
38
|
+
|
|
39
|
+
### Outlook
|
|
40
|
+
- `Inbox`
|
|
41
|
+
- `Sent`
|
|
42
|
+
- `Drafts`
|
|
43
|
+
- `Archive`
|
|
44
|
+
- `Junk`
|
|
45
|
+
- `Deleted`
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Messages Tool - Full Documentation
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
Email messages: search, read, mark_read, mark_unread, flag, unflag, move, archive, trash.
|
|
5
|
+
|
|
6
|
+
## Important
|
|
7
|
+
- **search** defaults to all configured accounts. Filter with `account` param.
|
|
8
|
+
- **read** returns clean plain text body (HTML stripped for LLM token savings)
|
|
9
|
+
- **UIDs are per-account and per-folder** - always specify account for modify operations
|
|
10
|
+
- Query language supports compound filters: `UNREAD SINCE 2024-01-01`
|
|
11
|
+
|
|
12
|
+
## Actions
|
|
13
|
+
|
|
14
|
+
### search
|
|
15
|
+
Search emails across all or filtered accounts.
|
|
16
|
+
```json
|
|
17
|
+
{"action": "search", "query": "UNREAD", "folder": "INBOX", "limit": 20}
|
|
18
|
+
```
|
|
19
|
+
```json
|
|
20
|
+
{"action": "search", "query": "UNREAD SINCE 2024-06-01", "account": "user@gmail.com"}
|
|
21
|
+
```
|
|
22
|
+
```json
|
|
23
|
+
{"action": "search", "query": "FROM boss@company.com", "limit": 5}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Query shortcuts:
|
|
27
|
+
- `UNREAD` / `UNSEEN` - unread emails
|
|
28
|
+
- `READ` / `SEEN` - read emails
|
|
29
|
+
- `FLAGGED` / `STARRED` - flagged emails
|
|
30
|
+
- `ALL` / `*` - all emails
|
|
31
|
+
- `SINCE YYYY-MM-DD` - emails after date
|
|
32
|
+
- `FROM email` - emails from sender
|
|
33
|
+
- `SUBJECT text` - emails matching subject
|
|
34
|
+
- `UNREAD SINCE YYYY-MM-DD` - compound filter
|
|
35
|
+
- `UNREAD FROM email` - compound filter
|
|
36
|
+
- Any other text is treated as subject search
|
|
37
|
+
|
|
38
|
+
### read
|
|
39
|
+
Read a single email by UID. Returns full body as clean text.
|
|
40
|
+
```json
|
|
41
|
+
{"action": "read", "account": "user@gmail.com", "uid": 12345, "folder": "INBOX"}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### mark_read
|
|
45
|
+
```json
|
|
46
|
+
{"action": "mark_read", "account": "user@gmail.com", "uids": [123, 456, 789]}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### mark_unread
|
|
50
|
+
```json
|
|
51
|
+
{"action": "mark_unread", "account": "user@gmail.com", "uid": 123}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### flag
|
|
55
|
+
Star/flag emails.
|
|
56
|
+
```json
|
|
57
|
+
{"action": "flag", "account": "user@gmail.com", "uids": [123, 456]}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### unflag
|
|
61
|
+
Remove star/flag from emails.
|
|
62
|
+
```json
|
|
63
|
+
{"action": "unflag", "account": "user@gmail.com", "uid": 123}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### move
|
|
67
|
+
Move emails to another folder.
|
|
68
|
+
```json
|
|
69
|
+
{"action": "move", "account": "user@gmail.com", "uids": [123], "destination": "[Gmail]/Important"}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### archive
|
|
73
|
+
Archive emails (auto-detects archive folder per provider).
|
|
74
|
+
```json
|
|
75
|
+
{"action": "archive", "account": "user@gmail.com", "uids": [123, 456]}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### trash
|
|
79
|
+
Delete emails (moves to trash).
|
|
80
|
+
```json
|
|
81
|
+
{"action": "trash", "account": "user@gmail.com", "uid": 123}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Parameters
|
|
85
|
+
- `action` - Action to perform (required)
|
|
86
|
+
- `account` - Account email filter (optional for search, required for modify)
|
|
87
|
+
- `query` - Search query string (default: UNSEEN)
|
|
88
|
+
- `folder` - Mailbox folder (default: INBOX)
|
|
89
|
+
- `limit` - Max search results (default: 20)
|
|
90
|
+
- `uid` - Single email UID
|
|
91
|
+
- `uids` - Multiple email UIDs for batch operations
|
|
92
|
+
- `destination` - Target folder for move action
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Send Tool - Full Documentation
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
Send new emails, reply to threads, and forward emails via SMTP.
|
|
5
|
+
|
|
6
|
+
## Important
|
|
7
|
+
- **reply** automatically sets In-Reply-To and References headers for threading
|
|
8
|
+
- **forward** includes original email body with separator
|
|
9
|
+
- `account` specifies which configured email to send from
|
|
10
|
+
- HTML is auto-generated from plain text body (basic markdown support)
|
|
11
|
+
|
|
12
|
+
## Actions
|
|
13
|
+
|
|
14
|
+
### new
|
|
15
|
+
Send a new email.
|
|
16
|
+
```json
|
|
17
|
+
{"action": "new", "account": "user@gmail.com", "to": "recipient@example.com", "subject": "Hello", "body": "Hi there!"}
|
|
18
|
+
```
|
|
19
|
+
```json
|
|
20
|
+
{"action": "new", "account": "user@gmail.com", "to": "a@example.com", "subject": "Update", "body": "See details.", "cc": "b@example.com", "bcc": "c@example.com"}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### reply
|
|
24
|
+
Reply to an email. Reads original email to set threading headers.
|
|
25
|
+
```json
|
|
26
|
+
{"action": "reply", "account": "user@gmail.com", "to": "sender@example.com", "body": "Thanks!", "uid": 12345}
|
|
27
|
+
```
|
|
28
|
+
```json
|
|
29
|
+
{"action": "reply", "account": "user@gmail.com", "to": "sender@example.com", "subject": "Re: Custom subject", "body": "Got it.", "uid": 12345, "folder": "INBOX"}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### forward
|
|
33
|
+
Forward an email. Original body is appended with separator.
|
|
34
|
+
```json
|
|
35
|
+
{"action": "forward", "account": "user@gmail.com", "to": "colleague@example.com", "body": "FYI, see below.", "uid": 12345}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Parameters
|
|
39
|
+
- `action` - Action to perform (required): new, reply, forward
|
|
40
|
+
- `account` - Sender account email (required)
|
|
41
|
+
- `to` - Recipient email address (required)
|
|
42
|
+
- `subject` - Email subject (required for new, optional for reply/forward)
|
|
43
|
+
- `body` - Email body text (required)
|
|
44
|
+
- `cc` - CC recipients (comma-separated, optional)
|
|
45
|
+
- `bcc` - BCC recipients (comma-separated, optional)
|
|
46
|
+
- `uid` - Original email UID (required for reply/forward)
|
|
47
|
+
- `folder` - Folder of original email (default: INBOX, for reply/forward)
|
|
48
|
+
|
|
49
|
+
## Notes
|
|
50
|
+
- Reply subject auto-prepends "Re:" if not already present
|
|
51
|
+
- Forward subject auto-prepends "Fwd:" if not already present
|
|
52
|
+
- Body supports basic markdown: `# heading`, `## heading`, `- list item`, `**bold**`
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Better Email MCP Server
|
|
3
|
+
* Using composite tools for human-friendly AI agent interactions
|
|
4
|
+
*/
|
|
5
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
6
|
+
export declare function initServer(): Promise<Server<{
|
|
7
|
+
method: string;
|
|
8
|
+
params?: {
|
|
9
|
+
[x: string]: unknown;
|
|
10
|
+
_meta?: {
|
|
11
|
+
[x: string]: unknown;
|
|
12
|
+
progressToken?: string | number | undefined;
|
|
13
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
14
|
+
taskId: string;
|
|
15
|
+
} | undefined;
|
|
16
|
+
} | undefined;
|
|
17
|
+
} | undefined;
|
|
18
|
+
}, {
|
|
19
|
+
method: string;
|
|
20
|
+
params?: {
|
|
21
|
+
[x: string]: unknown;
|
|
22
|
+
_meta?: {
|
|
23
|
+
[x: string]: unknown;
|
|
24
|
+
progressToken?: string | number | undefined;
|
|
25
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
26
|
+
taskId: string;
|
|
27
|
+
} | undefined;
|
|
28
|
+
} | undefined;
|
|
29
|
+
} | undefined;
|
|
30
|
+
}, {
|
|
31
|
+
[x: string]: unknown;
|
|
32
|
+
_meta?: {
|
|
33
|
+
[x: string]: unknown;
|
|
34
|
+
progressToken?: string | number | undefined;
|
|
35
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
36
|
+
taskId: string;
|
|
37
|
+
} | undefined;
|
|
38
|
+
} | undefined;
|
|
39
|
+
}>>;
|
|
40
|
+
//# sourceMappingURL=init-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init-server.d.ts","sourceRoot":"","sources":["../../src/init-server.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAA;AAkBlE,wBAAsB,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAwC/B"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Better Email MCP Server
|
|
3
|
+
* Using composite tools for human-friendly AI agent interactions
|
|
4
|
+
*/
|
|
5
|
+
import { readFileSync } from 'node:fs';
|
|
6
|
+
import { dirname, join } from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
9
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
10
|
+
import { loadConfig } from './tools/helpers/config.js';
|
|
11
|
+
import { registerTools } from './tools/registry.js';
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = dirname(__filename);
|
|
14
|
+
function getVersion() {
|
|
15
|
+
try {
|
|
16
|
+
const pkgPath = join(__dirname, '..', 'package.json');
|
|
17
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
18
|
+
return pkg.version ?? '0.0.0';
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return '0.0.0';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export async function initServer() {
|
|
25
|
+
// Load email accounts from environment
|
|
26
|
+
const accounts = loadConfig();
|
|
27
|
+
if (accounts.length === 0) {
|
|
28
|
+
console.error('EMAIL_CREDENTIALS environment variable is required');
|
|
29
|
+
console.error('Format: email1:password1,email2:password2');
|
|
30
|
+
console.error('');
|
|
31
|
+
console.error('Examples:');
|
|
32
|
+
console.error(' EMAIL_CREDENTIALS=user@gmail.com:abcd-efgh-ijkl-mnop');
|
|
33
|
+
console.error(' EMAIL_CREDENTIALS=user1@gmail.com:pass1,user2@outlook.com:pass2');
|
|
34
|
+
console.error('');
|
|
35
|
+
console.error('For Gmail: Enable 2FA, then create App Password at https://myaccount.google.com/apppasswords');
|
|
36
|
+
console.error('For Outlook: Enable 2FA, then create App Password in security settings');
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
console.error(`Loaded ${accounts.length} email account(s): ${accounts.map((a) => a.email).join(', ')}`);
|
|
40
|
+
// Create MCP server
|
|
41
|
+
const server = new Server({
|
|
42
|
+
name: '@n24q02m/better-email-mcp',
|
|
43
|
+
version: getVersion()
|
|
44
|
+
}, {
|
|
45
|
+
capabilities: {
|
|
46
|
+
tools: {},
|
|
47
|
+
resources: {}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
// Register composite tools
|
|
51
|
+
registerTools(server, accounts);
|
|
52
|
+
// Connect stdio transport
|
|
53
|
+
const transport = new StdioServerTransport();
|
|
54
|
+
await server.connect(transport);
|
|
55
|
+
return server;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=init-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init-server.js","sourceRoot":"","sources":["../../src/init-server.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAA;AAClE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAA;AAChF,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAA;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAEnD,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AACjD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAA;AAErC,SAAS,UAAU;IACjB,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,cAAc,CAAC,CAAA;QACrD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAA;QACtD,OAAO,GAAG,CAAC,OAAO,IAAI,OAAO,CAAA;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAA;IAChB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,uCAAuC;IACvC,MAAM,QAAQ,GAAG,UAAU,EAAE,CAAA;IAE7B,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,CAAC,KAAK,CAAC,oDAAoD,CAAC,CAAA;QACnE,OAAO,CAAC,KAAK,CAAC,2CAA2C,CAAC,CAAA;QAC1D,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;QACjB,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAAA;QAC1B,OAAO,CAAC,KAAK,CAAC,wDAAwD,CAAC,CAAA;QACvE,OAAO,CAAC,KAAK,CAAC,mEAAmE,CAAC,CAAA;QAClF,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;QACjB,OAAO,CAAC,KAAK,CAAC,8FAA8F,CAAC,CAAA;QAC7G,OAAO,CAAC,KAAK,CAAC,wEAAwE,CAAC,CAAA;QACvF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IAED,OAAO,CAAC,KAAK,CAAC,UAAU,QAAQ,CAAC,MAAM,sBAAsB,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAEvG,oBAAoB;IACpB,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB;QACE,IAAI,EAAE,2BAA2B;QACjC,OAAO,EAAE,UAAU,EAAE;KACtB,EACD;QACE,YAAY,EAAE;YACZ,KAAK,EAAE,EAAE;YACT,SAAS,EAAE,EAAE;SACd;KACF,CACF,CAAA;IAED,2BAA2B;IAC3B,aAAa,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;IAE/B,0BAA0B;IAC1B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAA;IAC5C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;IAC/B,OAAO,MAAM,CAAA;AACf,CAAC"}
|