mcp-mail-server 1.1.1 → 1.1.3

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.
Files changed (3) hide show
  1. package/README.md +195 -282
  2. package/dist/index.js +1 -1
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -1,49 +1,35 @@
1
1
  # MCP Mail Server
2
2
 
3
+ ![NPM Version](https://img.shields.io/npm/v/mcp-mail-server)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
3
6
  **Language:** English | [中文](README-zh.md)
4
7
 
5
- A Model Context Protocol (MCP) server that enables email operations through IMAP and SMTP protocols in Cursor AI. Features secure environment-based configuration for seamless email management.
6
-
7
- ### ✨ Features
8
-
9
- #### 📥 IMAP Features (Receive Emails)
10
- - Connect to IMAP email servers with TLS support
11
- - Open and manage multiple mailboxes (folders)
12
- - Advanced email search with multiple criteria
13
- - Retrieve messages by UID with full content parsing via mailparser
14
- - Mark messages as seen/unseen
15
- - Delete messages from server
16
- - Get message counts and mailbox information
17
- - List all available mailboxes
18
- - Get unseen and recent messages
19
- - Automatic connection management
20
- - Real-time connection status monitoring
21
-
22
- #### 📤 SMTP Features (Send Emails)
23
- - Connect to SMTP email servers
24
- - Send emails (text and HTML formats)
25
- - Support for CC and BCC recipients
26
- - SSL/TLS encryption support
27
- - Comprehensive error handling
28
-
29
- ### 📦 Installation
30
-
31
- #### Method 1: Install via npm
32
- ```bash
33
- npm install -g mcp-mail-server
34
- ```
8
+ A Model Context Protocol server for IMAP/SMTP email operations with Claude, Cursor, and other AI assistants.
35
9
 
36
- #### Method 2: Use with npx (Recommended)
37
- No installation required:
38
- ```bash
39
- npx mcp-mail-server
40
- ```
10
+ ## Features
41
11
 
42
- ### ⚙️ Cursor Configuration
12
+ - **IMAP Operations**: Search, read, and manage emails across mailboxes
13
+ - **SMTP Support**: Send emails with HTML/text content and attachments
14
+ - **Secure Configuration**: Environment-based setup with TLS/SSL support
15
+ - **AI-Friendly**: Natural language commands for email operations
16
+ - **Auto Connection Management**: Automatic IMAP/SMTP connection handling
17
+ - **Multi-Mailbox Support**: Access INBOX, Sent, and custom folders
43
18
 
44
- Add the following configuration to your Cursor MCP settings:
19
+ ## Quick Start
20
+
21
+ 1. **Install**: `npm install -g mcp-mail-server`
22
+ 2. **Configure** environment variables (see [Configuration](#configuration))
23
+ 3. **Add** to your MCP client configuration
24
+ 4. **Use** natural language: *"Show me unread emails from today"*
25
+
26
+ ## Installation
27
+
28
+ <details>
29
+ <summary>Claude Desktop</summary>
30
+
31
+ Add to your `claude_desktop_config.json`:
45
32
 
46
- #### Using npx (Recommended):
47
33
  ```json
48
34
  {
49
35
  "mcpServers": {
@@ -65,18 +51,25 @@ Add the following configuration to your Cursor MCP settings:
65
51
  }
66
52
  ```
67
53
 
68
- #### Using global installation:
54
+ </details>
55
+
56
+ <details>
57
+ <summary>Cursor</summary>
58
+
59
+ Add to your Cursor MCP settings:
60
+
69
61
  ```json
70
62
  {
71
63
  "mcpServers": {
72
64
  "mcp-mail-server": {
73
- "command": "mcp-mail-server",
65
+ "command": "npx",
66
+ "args": ["mcp-mail-server"],
74
67
  "env": {
75
68
  "IMAP_HOST": "your-imap-server.com",
76
69
  "IMAP_PORT": "993",
77
70
  "IMAP_SECURE": "true",
78
71
  "SMTP_HOST": "your-smtp-server.com",
79
- "SMTP_PORT": "465",
72
+ "SMTP_PORT": "465",
80
73
  "SMTP_SECURE": "true",
81
74
  "EMAIL_USER": "your-email@domain.com",
82
75
  "EMAIL_PASS": "your-password"
@@ -86,296 +79,216 @@ Add the following configuration to your Cursor MCP settings:
86
79
  }
87
80
  ```
88
81
 
89
- ### 🛠️ Available Tools
90
-
91
- #### Connection Management
92
-
93
- #### `connect_all`
94
- Connect to both IMAP and SMTP servers simultaneously using preconfigured settings.
95
-
96
- #### `get_connection_status`
97
- Check the current connection status of both IMAP and SMTP servers, including server information and current mailbox.
98
-
99
- #### `disconnect_all`
100
- Disconnect from both IMAP and SMTP servers.
101
-
102
- #### Mailbox Operations
103
-
104
- #### `open_mailbox`
105
- Open a specific mailbox (folder) for operations.
106
-
107
- **Parameters:**
108
- - `mailboxName` (string, optional): Name of the mailbox to open (default: "INBOX")
109
- - `readOnly` (boolean, optional): Open mailbox in read-only mode (default: false)
110
-
111
- #### `list_mailboxes`
112
- List all available mailboxes (folders) on the server.
82
+ </details>
113
83
 
114
- #### Email Search Tools
84
+ <details>
85
+ <summary>Other MCP Clients</summary>
115
86
 
116
- #### `search_messages`
117
- Search messages using IMAP search criteria.
87
+ For global installation:
118
88
 
119
- **Parameters:**
120
- - `criteria` (array, optional): IMAP search criteria (default: ["ALL"])
121
- - Examples: `["UNSEEN"]`, `["SINCE", "2024-01-01"]`, `["FROM", "sender@example.com"]`
122
-
123
- #### `search_by_sender`
124
- Search messages from a specific sender.
125
-
126
- **Parameters:**
127
- - `sender` (string): Email address of the sender to search for
128
-
129
- #### `search_by_subject`
130
- Search messages by subject keywords.
131
-
132
- **Parameters:**
133
- - `subject` (string): Keywords to search in email subject
134
-
135
- #### `search_by_body`
136
- Search messages containing specific text in the body.
137
-
138
- **Parameters:**
139
- - `text` (string): Text to search for in message body
140
-
141
- #### `search_since_date`
142
- Search messages since a specific date.
143
-
144
- **Parameters:**
145
- - `date` (string): Date in various formats like "April 20, 2010", "20-Apr-2010", or "2010-04-20"
146
-
147
- #### `search_larger_than`
148
- Search messages larger than specified size.
149
-
150
- **Parameters:**
151
- - `size` (number): Size in bytes
152
-
153
- #### `search_with_keyword`
154
- Search messages with specific keyword/flag.
155
-
156
- **Parameters:**
157
- - `keyword` (string): Keyword to search for
158
-
159
- #### `search_unread_from_sender`
160
- Search unread messages from a specific sender (demonstrates AND logic).
161
-
162
- **Parameters:**
163
- - `sender` (string): Email address of the sender
164
-
165
- #### `get_messages`
166
- Retrieve multiple messages by their UIDs.
167
-
168
- **Parameters:**
169
- - `uids` (array): Array of message UIDs to retrieve
170
- - `markSeen` (boolean, optional): Mark messages as seen when retrieving (default: false)
89
+ ```bash
90
+ npm install -g mcp-mail-server
91
+ ```
171
92
 
172
- #### `get_message`
173
- Retrieve complete content of a specific email by UID.
93
+ Then configure with:
174
94
 
175
- **Parameters:**
176
- - `uid` (number): Message UID to retrieve
177
- - `markSeen` (boolean, optional): Mark message as seen when retrieving (default: false)
95
+ ```json
96
+ {
97
+ "mcpServers": {
98
+ "mcp-mail-server": {
99
+ "command": "mcp-mail-server"
100
+ }
101
+ }
102
+ }
103
+ ```
178
104
 
179
- #### `delete_message`
180
- Delete a specific email message by UID.
105
+ </details>
181
106
 
182
- **Parameters:**
183
- - `uid` (number): Message UID to delete
107
+ ## Available Tools
184
108
 
185
- #### `get_message_count`
186
- Get the total number of messages in current mailbox.
109
+ | Tool | Description |
110
+ |------|-------------|
111
+ | `connect_all` | Connect to both IMAP and SMTP servers |
112
+ | `get_connection_status` | Check connection status and server info |
113
+ | `disconnect_all` | Disconnect from all servers |
114
+ | `open_mailbox` | Open specific mailbox/folder |
115
+ | `list_mailboxes` | List available mail folders |
116
+ | `search_messages` | Search emails with IMAP criteria |
117
+ | `search_by_sender` | Find emails from specific sender |
118
+ | `search_by_subject` | Search by subject keywords |
119
+ | `search_by_body` | Search message content |
120
+ | `search_since_date` | Find emails since date |
121
+ | `search_larger_than` | Find emails by size |
122
+ | `get_message` | Retrieve email by UID |
123
+ | `get_messages` | Retrieve multiple emails |
124
+ | `delete_message` | Delete email by UID |
125
+ | `get_unseen_messages` | Get all unread emails |
126
+ | `get_recent_messages` | Get recent emails |
127
+ | `send_email` | Send email via SMTP |
187
128
 
188
- #### `get_unseen_messages`
189
- Get all unseen (unread) messages in current mailbox.
129
+ <details>
130
+ <summary>Detailed Tool Parameters</summary>
190
131
 
191
- #### `get_recent_messages`
192
- Get all recent messages in current mailbox.
132
+ ### Connection Management
133
+ - **connect_all**: No parameters required
134
+ - **get_connection_status**: No parameters required
135
+ - **disconnect_all**: No parameters required
193
136
 
194
- #### Email Sending
137
+ ### Mailbox Operations
138
+ - **open_mailbox**: `mailboxName` (string, default: "INBOX"), `readOnly` (boolean)
139
+ - **list_mailboxes**: No parameters required
195
140
 
196
- #### `send_email`
197
- Send an email via SMTP.
141
+ ### Search Operations
142
+ - **search_messages**: `criteria` (array, IMAP search criteria)
143
+ - **search_by_sender**: `sender` (string, email address)
144
+ - **search_by_subject**: `subject` (string, keywords)
145
+ - **search_by_body**: `text` (string, search text)
146
+ - **search_since_date**: `date` (string, date format)
147
+ - **search_larger_than**: `size` (number, bytes)
198
148
 
199
- **Parameters:**
200
- - `to` (string): Recipient email address(es), comma-separated
201
- - `subject` (string): Email subject
202
- - `text` (string, optional): Plain text email body
203
- - `html` (string, optional): HTML email body
204
- - `cc` (string, optional): CC recipients, comma-separated
205
- - `bcc` (string, optional): BCC recipients, comma-separated
149
+ ### Message Operations
150
+ - **get_message**: `uid` (number), `markSeen` (boolean, optional)
151
+ - **get_messages**: `uids` (array), `markSeen` (boolean, optional)
152
+ - **delete_message**: `uid` (number)
206
153
 
154
+ ### Email Sending
155
+ - **send_email**: `to` (string), `subject` (string), `text` (string, optional), `html` (string, optional), `cc` (string, optional), `bcc` (string, optional)
207
156
 
208
- ### 💡 Usage Examples
157
+ </details>
209
158
 
210
- **Note: This project uses preconfigured email server settings via environment variables.**
211
159
 
212
- In Cursor, you can use natural language commands:
160
+ ## Usage Examples
213
161
 
214
- #### 🚀 Quick Start
162
+ Use natural language commands with your AI assistant:
215
163
 
216
- 1. Connect to all email servers at once:
217
- ```
218
- Connect to email servers
219
- ```
220
- or
221
- ```
222
- Connect to all mail servers
223
- ```
164
+ ### Basic Operations
165
+ - *"Connect to my email servers"*
166
+ - *"Show me all unread emails"*
167
+ - *"Search for emails from boss@company.com"*
168
+ - *"Send an email to team@company.com about the meeting"*
224
169
 
225
- 2. Check connection status:
226
- ```
227
- Show email connection status
228
- ```
229
- or
230
- ```
231
- Check mail server connections
232
- ```
170
+ ### Advanced Searches
171
+ - *"Find emails with 'urgent' in the subject from last week"*
172
+ - *"Show me large emails over 5MB"*
173
+ - *"Get all emails from the Sales folder"*
233
174
 
234
- #### 📥 Receiving Emails
175
+ ### Email Management
176
+ - *"Delete the email with UID 123"*
177
+ - *"Mark recent emails as read"*
178
+ - *"List all my email folders"*
235
179
 
236
- **Note: IMAP connection is established automatically when needed**
180
+ ## Configuration
237
181
 
238
- 1. Open a mailbox:
239
- ```
240
- Open INBOX mailbox
241
- ```
242
- or
243
- ```
244
- Open Sent mailbox in read-only mode
245
- ```
182
+ ### Environment Variables
246
183
 
247
- 2. Search for emails:
248
- ```
249
- Search for unseen messages
250
- ```
251
- or
252
- ```
253
- Search for messages from sender@example.com
254
- ```
255
- or
256
- ```
257
- Search for messages with "urgent" in the body
258
- ```
259
- or
260
- ```
261
- Search for messages larger than 1MB
262
- ```
263
- or
264
- ```
265
- Search for unread messages from boss@company.com
266
- ```
184
+ **⚠️ All variables are required**
267
185
 
268
- 3. Get specific email by UID:
269
- ```
270
- Show me the content of email with UID 123
271
- ```
186
+ | Variable | Description | Example |
187
+ |----------|-------------|---------|
188
+ | `IMAP_HOST` | IMAP server address | `imap.gmail.com` |
189
+ | `IMAP_PORT` | IMAP port number | `993` |
190
+ | `IMAP_SECURE` | Enable TLS | `true` |
191
+ | `SMTP_HOST` | SMTP server address | `smtp.gmail.com` |
192
+ | `SMTP_PORT` | SMTP port number | `465` |
193
+ | `SMTP_SECURE` | Enable SSL | `true` |
194
+ | `EMAIL_USER` | Email username | `your-email@gmail.com` |
195
+ | `EMAIL_PASS` | Email password/app password | `your-app-password` |
272
196
 
273
- 4. Get all unseen messages:
274
- ```
275
- Show me all unread emails
276
- ```
197
+ ### Common Email Providers
277
198
 
278
- 5. List available mailboxes:
279
- ```
280
- Show me all email folders
281
- ```
199
+ <details>
200
+ <summary>Gmail Configuration</summary>
282
201
 
283
- 6. Delete email by UID:
284
- ```
285
- Delete email with UID 123
202
+ ```bash
203
+ IMAP_HOST=imap.gmail.com
204
+ IMAP_PORT=993
205
+ IMAP_SECURE=true
206
+ SMTP_HOST=smtp.gmail.com
207
+ SMTP_PORT=465
208
+ SMTP_SECURE=true
209
+ EMAIL_USER=your-email@gmail.com
210
+ EMAIL_PASS=your-app-password
286
211
  ```
287
212
 
288
- #### 📤 Sending Emails
213
+ **Note**: Use [App Passwords](https://support.google.com/accounts/answer/185833) instead of your regular password.
289
214
 
290
- **Note: SMTP connection is established automatically when needed**
215
+ </details>
291
216
 
292
- 1. Send simple email:
293
- ```
294
- Send email to recipient@example.com with subject "Test Email" and message "Hello, this is a test email"
295
- ```
217
+ <details>
218
+ <summary>Outlook/Hotmail Configuration</summary>
296
219
 
297
- 2. Send HTML email:
298
- ```
299
- Send HTML email to recipient@example.com with subject "Welcome" and HTML content "<h1>Welcome!</h1><p>This is an HTML email</p>"
220
+ ```bash
221
+ IMAP_HOST=outlook.office365.com
222
+ IMAP_PORT=993
223
+ IMAP_SECURE=true
224
+ SMTP_HOST=smtp.office365.com
225
+ SMTP_PORT=587
226
+ SMTP_SECURE=true
227
+ EMAIL_USER=your-email@outlook.com
228
+ EMAIL_PASS=your-password
300
229
  ```
301
230
 
302
- 3. Send email with CC and BCC:
303
- ```
304
- Send email to primary@example.com, CC to cc@example.com, BCC to bcc@example.com with subject "Team Update" and message "Weekly team update"
305
- ```
231
+ </details>
306
232
 
307
- ### 🔧 Environment Configuration
233
+ ### Security Notes
308
234
 
309
- #### Required Environment Variables
235
+ - **Use App Passwords**: Enable 2FA and use app-specific passwords when available
236
+ - **TLS/SSL Required**: Always use secure connections (IMAP_SECURE=true, SMTP_SECURE=true)
237
+ - **Environment Variables**: Never hardcode credentials in configuration files
310
238
 
311
- **⚠️ All environment variables are required - no defaults provided!**
239
+ ## Development
312
240
 
313
- | Variable | Description | Example Value |
314
- |----------|-------------|---------------|
315
- | `IMAP_HOST` | IMAP server address | your-imap-server.com |
316
- | `IMAP_PORT` | IMAP port number | 993 |
317
- | `IMAP_SECURE` | Enable TLS (true/false) | true |
318
- | `SMTP_HOST` | SMTP server address | your-smtp-server.com |
319
- | `SMTP_PORT` | SMTP port number | 465 |
320
- | `SMTP_SECURE` | Enable SSL (true/false) | true |
321
- | `EMAIL_USER` | Email username | your-email@domain.com |
322
- | `EMAIL_PASS` | Email password | your-password |
241
+ <details>
242
+ <summary>Local Development Setup</summary>
323
243
 
324
- #### Configuration Validation
244
+ 1. **Clone the repository**:
245
+ ```bash
246
+ git clone https://github.com/yunfeizhu/mcp-mail-server.git
247
+ cd mcp-mail-server
248
+ ```
325
249
 
326
- - Server will fail to start if any required environment variable is missing
327
- - Boolean values must be `true` or `false` (case-insensitive)
328
- - Port numbers must be valid integers
329
- - Configuration summary is displayed on startup (passwords are hidden)
250
+ 2. **Install dependencies**:
251
+ ```bash
252
+ npm install
253
+ ```
330
254
 
331
- ### ⚠️ Security Considerations
255
+ 3. **Build the project**:
256
+ ```bash
257
+ npm run build
258
+ ```
332
259
 
333
- - Use app-specific passwords when available (Gmail, Outlook, etc.)
334
- - Ensure secure network connections in production
335
- - Environment variables are the recommended configuration method
336
- - TLS/SSL encryption is strongly recommended for both protocols
260
+ 4. **Set environment variables**:
261
+ ```bash
262
+ export IMAP_HOST=your-imap-server.com
263
+ export IMAP_PORT=993
264
+ export IMAP_SECURE=true
265
+ export SMTP_HOST=your-smtp-server.com
266
+ export SMTP_PORT=465
267
+ export SMTP_SECURE=true
268
+ export EMAIL_USER=your-email@domain.com
269
+ export EMAIL_PASS=your-password
270
+ ```
337
271
 
338
- ### 🔨 Development
272
+ 5. **Run the server**:
273
+ ```bash
274
+ npm start
275
+ ```
339
276
 
340
- To modify or develop this project:
277
+ </details>
341
278
 
342
- 1. Clone the repository:
343
- ```bash
344
- git clone https://github.com/yunfeizhu/mcp-mail-server.git
345
- cd mcp-mail-server
346
- ```
279
+ ## Contributing
347
280
 
348
- 2. Install dependencies:
349
- ```bash
350
- npm install
351
- ```
281
+ Contributions are welcome! Please feel free to submit a Pull Request.
352
282
 
353
- 3. Build the project:
354
- ```bash
355
- npm run build
356
- ```
283
+ ## License
357
284
 
358
- 4. Local testing:
359
- ```bash
360
- # Set environment variables
361
- export IMAP_HOST=your-imap-server.com
362
- export IMAP_PORT=993
363
- export IMAP_SECURE=true
364
- export SMTP_HOST=your-smtp-server.com
365
- export SMTP_PORT=465
366
- export SMTP_SECURE=true
367
- export EMAIL_USER=your-email@domain.com
368
- export EMAIL_PASS=your-password
369
-
370
- # Run the server
371
- npm start
372
- ```
285
+ MIT License - see [LICENSE](LICENSE) file for details.
373
286
 
374
- ### 📊 Package Information
287
+ ---
375
288
 
376
- - **Package Name**: `mcp-mail-server`
377
- - **Executable**: `mcp-mail-server`
378
- - **Node.js Version**: >=18.0.0
379
- - **License**: MIT
380
- - **Repository**: [GitHub](https://github.com/yunfeizhu/mcp-mail-server)
289
+ **Package Information:**
290
+ - Package: `mcp-mail-server`
291
+ - Node.js: 18.0.0
292
+ - Repository: [GitHub](https://github.com/yunfeizhu/mcp-mail-server)
293
+ - Issues: [Report bugs](https://github.com/yunfeizhu/mcp-mail-server/issues)
381
294
 
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{Server as e}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as t}from"@modelcontextprotocol/sdk/server/stdio.js";import{ListToolsRequestSchema as r,CallToolRequestSchema as n}from"@modelcontextprotocol/sdk/types.js";import s from"imap";import{EventEmitter as i}from"events";import{simpleParser as o}from"mailparser";import a from"nodemailer";class IMAPClient extends i{imap=null;config;connected=!1;authenticated=!1;currentBox=null;constructor(e){super(),this.config=e}async connect(){return new Promise((e,t)=>{console.error(`[IMAP] Connecting to ${this.config.host}:${this.config.port} (TLS: ${this.config.tls})`);const r={user:this.config.username,password:this.config.password,host:this.config.host,port:this.config.port,tls:this.config.tls||!1,connTimeout:this.config.connTimeout||1e4,authTimeout:this.config.authTimeout||5e3,keepalive:!1!==this.config.keepalive};this.imap=new s(r),this.imap.once("ready",async()=>{console.error("[IMAP] Connection ready"),this.connected=!0,this.authenticated=!0;try{await this.openBox("INBOX",!0),console.error("[IMAP] Auto-opened INBOX")}catch(e){console.error("[IMAP] Failed to auto-open INBOX:",e instanceof Error?e.message:String(e))}e()}),this.imap.once("error",e=>{console.error("[IMAP] Connection error:",e.message),t(new Error(`IMAP connection failed: ${e.message}`))}),this.imap.once("end",()=>{console.error("[IMAP] Connection ended"),this.connected=!1,this.authenticated=!1,this.currentBox=null}),this.imap.connect()})}async openBox(e="INBOX",t=!1){if(!this.imap||!this.authenticated)throw new Error("Not connected or authenticated");return new Promise((r,n)=>{this.imap.openBox(e,t,(t,s)=>{if(t)return console.error(`[IMAP] Failed to open box ${e}:`,t.message),void n(new Error(`Failed to open mailbox: ${t.message}`));console.error(`[IMAP] Opened box ${e}`),this.currentBox=e;const i={name:e,messages:{total:s.messages.total,new:s.messages.new,unseen:s.messages.unseen},permFlags:s.permFlags,uidvalidity:s.uidvalidity,uidnext:s.uidnext};r(i)})})}async getBoxes(){if(!this.imap||!this.authenticated)throw new Error("Not connected or authenticated");return new Promise((e,t)=>{this.imap.getBoxes((r,n)=>{r?t(new Error(`Failed to get boxes: ${r.message}`)):e(n)})})}async search(e=["ALL"]){if(!this.imap)throw new Error("Not connected to IMAP server");return this.currentBox||await this.openBox("INBOX",!0),new Promise((t,r)=>{this.imap.search([e],(e,n)=>{if(e)return console.error("[IMAP] Search failed:",e.message),void r(new Error(`Search failed: ${e.message}`));console.error(`[IMAP] Search found ${n.length} messages`),t(n)})})}async fetchMessages(e,t={}){if(!this.imap)throw new Error("Not connected to IMAP server");this.currentBox||await this.openBox("INBOX",!0);const r={bodies:t.bodies||["HEADER","TEXT"],struct:!1!==t.struct,envelope:!1!==t.envelope,markSeen:t.markSeen||!1,...t};return new Promise((t,n)=>{const s=[],i=new Map;if(0===e.length)return void t(s);const a=this.imap.fetch(e,r);a.on("message",(e,t)=>{console.error(`[IMAP] Processing message ${t}`);let r={},n="";const s=[],o={uid:0,id:t,flags:[],date:"",size:0};e.on("body",(e,t)=>{const i=[];e.on("data",e=>{i.push(e),s.push(e)}),e.once("end",()=>{const e=Buffer.concat(i);if("HEADER"===t.which){const t=e.toString("utf8");r=this.parseHeaders(t)}else"TEXT"===t.which&&(n=e.toString("utf8"))})}),e.once("attributes",e=>{o.uid=e.uid,o.flags=e.flags||[];const t=e.date||new Date;o.date=t.toLocaleString("zh-CN",{timeZone:"Asia/Shanghai",year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit"}),o.size=e.size||0}),e.once("end",()=>{console.error(`[IMAP] Message ${t} processed, preparing for parse`),i.set(t,{message:o,headers:r,body:n,rawBuffer:Buffer.concat(s)})})}),a.once("error",e=>{console.error("[IMAP] Fetch error:",e.message),n(new Error(`Fetch failed: ${e.message}`))}),a.once("end",async()=>{console.error(`[IMAP] Fetch completed, parsing ${i.size} messages`);for(const[e,t]of i)try{const e=await o(t.rawBuffer),r=e=>e?Array.isArray(e)?e.map(e=>n(e)).filter(Boolean).join(", "):n(e):"",n=e=>{if(!e)return"";if("string"==typeof e){const t=e.match(/<([^>]+)>/)||e.match(/([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/);return t?t[1]:e}if(e&&"object"==typeof e){if(e.address)return e.address;if(e.text){const t=e.text.match(/<([^>]+)>/)||e.text.match(/([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/);return t?t[1]:e.text}}return""};s.push({...t.message,subject:e.subject||"No Subject",from:r(e.from),to:r(e.to),cc:r(e.cc)||void 0,bcc:r(e.bcc)||void 0,text:e.text})}catch(r){console.error(`[IMAP] Failed to parse message ${e}:`,r),s.push({...t.message,subject:t.headers.subject||"Parse Failed",from:t.headers.from||"",to:t.headers.to||"",cc:t.headers.cc||void 0,bcc:t.headers.bcc||void 0,text:t.body.trim()})}console.error(`[IMAP] All messages parsed, returning ${s.length} messages`),t(s)})})}async getMessage(e){const t=await this.fetchMessages([e]);if(0===t.length)throw new Error(`Message with UID ${e} not found`);return t[0]}async deleteMessage(e){if(!this.imap)throw new Error("Not connected to IMAP server");return this.currentBox||await this.openBox("INBOX",!1),new Promise((t,r)=>{this.imap.addFlags(e,["\\Deleted"],n=>{if(n)return console.error(`[IMAP] Failed to mark message ${e} as deleted:`,n.message),void r(new Error(`Failed to delete message: ${n.message}`));console.error(`[IMAP] Message ${e} marked for deletion`),this.imap.expunge(n=>{if(n)return console.error("[IMAP] Failed to expunge:",n.message),void r(new Error(`Failed to expunge deleted messages: ${n.message}`));console.error(`[IMAP] Message ${e} deleted successfully`),t()})})})}async getMessageCount(){return this.currentBox||await this.openBox("INBOX",!0),(await this.search(["ALL"])).length}async getUnseenMessages(){const e=await this.search(["UNSEEN"]);return this.fetchMessages(e)}async getRecentMessages(){const e=await this.search(["RECENT"]);return this.fetchMessages(e)}parseHeaders(e){const t={},r=e.split("\r\n");let n="",s="";for(const e of r)if(e.match(/^\s/)&&n)s+=" "+e.trim();else{n&&(t[n.toLowerCase()]=s.trim());const r=e.indexOf(":");r>-1?(n=e.substring(0,r).trim(),s=e.substring(r+1).trim()):(n="",s="")}return n&&(t[n.toLowerCase()]=s.trim()),t}async disconnect(){if(this.imap)return this.connected?new Promise((e,t)=>{const r=setTimeout(()=>{console.error("[IMAP] Disconnect timeout, forcing cleanup"),this.connected=!1,this.authenticated=!1,this.currentBox=null,this.imap=null,e()},5e3);this.imap.once("end",()=>{clearTimeout(r),console.error("[IMAP] Disconnected"),this.connected=!1,this.authenticated=!1,this.currentBox=null,this.imap=null,e()}),this.imap.once("error",t=>{clearTimeout(r),console.error("[IMAP] Disconnect error:",t.message),this.connected=!1,this.authenticated=!1,this.currentBox=null,this.imap=null,e()});try{this.imap.end()}catch(t){clearTimeout(r),console.error("[IMAP] Error calling end():",t),this.connected=!1,this.authenticated=!1,this.currentBox=null,this.imap=null,e()}}):(this.imap=null,this.authenticated=!1,void(this.currentBox=null))}isConnected(){return this.connected&&this.authenticated}getCurrentBox(){return this.currentBox}}class SMTPClient{transporter=null;config;constructor(e){this.config=e}async connect(){this.transporter=a.createTransport({host:this.config.host,port:this.config.port,secure:this.config.secure||!1,auth:{user:this.config.username,pass:this.config.password}});try{this.transporter&&await this.transporter.verify()}catch(e){throw new Error(`SMTP connection failed: ${e instanceof Error?e.message:String(e)}`)}}async sendMail(e){if(!this.transporter)throw new Error("SMTP client not connected");const t={from:e.from||this.config.username,to:Array.isArray(e.to)?e.to.join(", "):e.to,cc:e.cc?Array.isArray(e.cc)?e.cc.join(", "):e.cc:void 0,bcc:e.bcc?Array.isArray(e.bcc)?e.bcc.join(", "):e.bcc:void 0,subject:e.subject,text:e.text,html:e.html,attachments:e.attachments};try{const e=await this.transporter.sendMail(t);return{messageId:e.messageId,response:e.response,accepted:e.accepted||[],rejected:e.rejected||[]}}catch(e){throw new Error(`Failed to send email: ${e instanceof Error?e.message:String(e)}`)}}async disconnect(){if(this.transporter)try{this.transporter.close(),console.error("[SMTP] Disconnected successfully")}catch(e){console.error("[SMTP] Error during disconnect:",e instanceof Error?e.message:String(e))}finally{this.transporter=null}}}function c(e,t){const r=process.env[e];if(!r)throw new Error(`Missing required environment variable: ${e}. Please set this variable in your MCP server configuration.`);return r}function h(e){const t=process.env[e];if(!t)throw new Error(`Missing required environment variable: ${e}. Please set this variable to 'true' or 'false' in your MCP server configuration.`);if("true"!==t.toLowerCase()&&"false"!==t.toLowerCase())throw new Error(`Invalid boolean value for environment variable ${e}: ${t}. Must be 'true' or 'false'.`);return"true"===t.toLowerCase()}function l(e){const t=process.env[e];if(!t)throw new Error(`Missing required environment variable: ${e}. Please set this variable to a valid number in your MCP server configuration.`);const r=parseInt(t,10);if(isNaN(r))throw new Error(`Invalid number value for environment variable ${e}: ${t}. Must be a valid number.`);return r}const d={IMAP:{host:c("IMAP_HOST"),port:l("IMAP_PORT"),username:c("EMAIL_USER"),password:c("EMAIL_PASS"),tls:h("IMAP_SECURE")},SMTP:{host:c("SMTP_HOST"),port:l("SMTP_PORT"),username:c("EMAIL_USER"),password:c("EMAIL_PASS"),secure:h("SMTP_SECURE")}};(new class{server;imapClient=null;smtpClient=null;isInitializing=!1;formatError(e,t){return`${t}: ${e instanceof Error?e.message:String(e)}`}constructor(){this.validateConfig(),this.server=new e({name:"mcp-mail",version:"1.0.0"},{capabilities:{tools:{}}}),this.setupToolHandlers(),this.setupErrorHandling()}setupErrorHandling(){this.server.onerror=e=>console.error("[MCP Error]",e),process.on("SIGINT",async()=>{this.imapClient&&await this.imapClient.disconnect(),this.smtpClient&&await this.smtpClient.disconnect(),await this.server.close(),process.exit(0)})}setupToolHandlers(){this.server.setRequestHandler(r,async()=>({tools:[{name:"connect_all",description:"Connect to both IMAP and SMTP servers simultaneously",inputSchema:{type:"object",properties:{}}},{name:"list_mailboxes",description:"List all available mailboxes (folders). Auto-connects if not already connected.",inputSchema:{type:"object",properties:{}}},{name:"open_mailbox",description:"Open a specific mailbox (folder) and optionally retrieve sent mailbox info. Due to IMAP protocol limitations, only one mailbox stays open. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{mailboxName:{type:"string",description:"Name of the mailbox to open (default: INBOX)",default:"INBOX"},readOnly:{type:"boolean",description:"Open mailbox in read-only mode (default: false)",default:!1},openSent:{type:"boolean",description:"Also retrieve sent mailbox information (default: true)",default:!0}}}},{name:"get_message_count",description:"Get the total number of messages in current mailbox. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{}}},{name:"get_unseen_messages",description:"Get all unseen (unread) messages. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{}}},{name:"get_recent_messages",description:"Get all recent messages. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{}}},{name:"search_messages",description:"Search messages using IMAP search criteria. Auto-connects and opens INBOX if not already connected.",inputSchema:{type:"object",properties:{criteria:{type:"array",description:'IMAP search criteria array. Examples: ["UNSEEN"], ["FROM", "user@example.com"], ["SUBJECT", "meeting"], ["BODY", "urgent"], ["SINCE", "April 20, 2010"], ["LARGER", "1000"], ["KEYWORD", "Important"], ["HEADER", "X-Custom", "value"], ["OR", "UNSEEN", ["SINCE", "April 20, 2010"]], ["!SEEN"] (negation). Default: searches ALL messages.',items:{}}}}},{name:"search_by_sender",description:"Search messages from a specific sender. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{sender:{type:"string",description:"Email address of the sender to search for"}},required:["sender"]}},{name:"search_by_subject",description:"Search messages by subject keywords. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{subject:{type:"string",description:"Keywords to search in email subject"}},required:["subject"]}},{name:"search_since_date",description:"Search messages since a specific date. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{date:{type:"string",description:'Date in various formats like "April 20, 2010", "20-Apr-2010", or "2010-04-20"'}},required:["date"]}},{name:"search_unread_from_sender",description:"Search unread messages from a specific sender (demonstrates AND logic). Auto-connects if not already connected.",inputSchema:{type:"object",properties:{sender:{type:"string",description:"Email address of the sender"}},required:["sender"]}},{name:"search_by_body",description:"Search messages containing specific text in the body. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{text:{type:"string",description:"Text to search for in message body"}},required:["text"]}},{name:"search_larger_than",description:"Search messages larger than specified size. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{size:{type:"number",description:"Size in bytes"}},required:["size"]}},{name:"search_with_keyword",description:"Search messages with specific keyword/flag. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{keyword:{type:"string",description:"Keyword to search for"}},required:["keyword"]}},{name:"get_messages",description:"Retrieve multiple messages by their UIDs. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{uids:{type:"array",description:"Array of message UIDs to retrieve",items:{type:"number"}},markSeen:{type:"boolean",description:"Mark messages as seen when retrieving (default: false)",default:!1}},required:["uids"]}},{name:"get_message",description:"Retrieve a specific email message by UID. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{uid:{type:"number",description:"Message UID to retrieve"},markSeen:{type:"boolean",description:"Mark message as seen when retrieving (default: false)",default:!1}},required:["uid"]}},{name:"send_email",description:"Send an email via SMTP. Auto-connects to SMTP server if not already connected.",inputSchema:{type:"object",properties:{to:{type:"string",description:"Recipient email address(es), comma-separated"},subject:{type:"string",description:"Email subject"},text:{type:"string",description:"Plain text email body"},html:{type:"string",description:"HTML email body (optional)"},cc:{type:"string",description:"CC recipients, comma-separated (optional)"},bcc:{type:"string",description:"BCC recipients, comma-separated (optional)"}},required:["to","subject"]}},{name:"delete_message",description:"Delete a specific email message by UID. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{uid:{type:"number",description:"Message UID to delete"}},required:["uid"]}},{name:"get_connection_status",description:"Check the current connection status of both IMAP and SMTP servers.",inputSchema:{type:"object",properties:{}}},{name:"disconnect_all",description:"Disconnect from both IMAP and SMTP servers. Only disconnects if currently connected.",inputSchema:{type:"object",properties:{}}}]})),this.server.setRequestHandler(n,async e=>{const{name:t,arguments:r}=e.params;try{switch(t){case"open_mailbox":return await this.handleOpenMailbox(r);case"list_mailboxes":return await this.handleListMailboxes();case"search_messages":return await this.handleSearchMessages(r);case"search_by_sender":return await this.handleSearchBySender(r);case"search_by_subject":return await this.handleSearchBySubject(r);case"search_since_date":return await this.handleSearchSinceDate(r);case"search_unread_from_sender":return await this.handleSearchUnreadFromSender(r);case"search_by_body":return await this.handleSearchByBody(r);case"search_larger_than":return await this.handleSearchLargerThan(r);case"search_with_keyword":return await this.handleSearchWithKeyword(r);case"get_messages":return await this.handleGetMessages(r);case"get_message":return await this.handleGetMessage(r);case"delete_message":return await this.handleDeleteMessage(r);case"get_message_count":return await this.handleGetMessageCount();case"get_unseen_messages":return await this.handleGetUnseenMessages();case"get_recent_messages":return await this.handleGetRecentMessages();case"get_connection_status":return await this.handleGetConnectionStatus();case"send_email":return await this.handleSendEmail(r);case"connect_all":return await this.handleConnectAll();case"disconnect_all":return await this.handleDisconnectAll();default:throw new Error(`Unknown tool: ${t}`)}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:String(e)}`}]}}})}async ensureIMAPConnection(){if(!this.imapClient||!this.imapClient.isConnected())if(this.isInitializing)for(;this.isInitializing;)await new Promise(e=>setTimeout(e,100));else{this.isInitializing=!0;try{const e=d.IMAP;console.error(`[IMAP] Auto-connecting to ${e.host}:${e.port}`),this.imapClient=new IMAPClient(e),await this.imapClient.connect(),console.error("[IMAP] Auto-connection successful")}finally{this.isInitializing=!1}}}async ensureSMTPConnection(){if(this.smtpClient)return;const e=d.SMTP;console.error(`[SMTP] Auto-connecting to ${e.host}:${e.port}`),this.smtpClient=new SMTPClient(e),await this.smtpClient.connect(),console.error("[SMTP] Auto-connection successful")}async ensureRequiredConnections(e=!1,t=!1){e&&await this.ensureIMAPConnection(),t&&await this.ensureSMTPConnection()}async handleOpenMailbox(e){await this.ensureRequiredConnections(!0,!1);const t=e.mailboxName||"INBOX",r=e.readOnly||!1,n=!1!==e.openSent;try{const e={},s=await this.imapClient.openBox(t,r);if(e[t]=s,e.currentlyOpen=t,n&&"INBOX.Sent"!==t&&"Sent"!==t&&"SENT"!==t)try{const n=["INBOX.Sent","Sent","SENT","Sent Items","Sent Messages","已发送"];let s=!1;for(const i of n)try{const n=await this.imapClient.openBox(i,!0);e[i]=n,s=!0,await this.imapClient.openBox(t,r),e.currentlyOpen=t,e.note=`Retrieved info from both ${t} and ${i}. Currently open: ${t}`;break}catch(e){continue}s||(e.sentBoxWarning="Could not find any sent mailbox")}catch(t){e.sentBoxError=`Failed to access sent mailbox: ${t instanceof Error?t.message:String(t)}`}return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Failed to open mailbox"))}}async handleListMailboxes(){await this.ensureRequiredConnections(!0,!1);try{const e=await this.imapClient.getBoxes(),t=(e,r="",n=new Set)=>{if(n.has(e))return{name:r,circular:!0};n.add(e);const s={name:r,attribs:e.attribs||[],delimiter:e.delimiter||".",selectable:!e.attribs?.includes("\\Noselect")};if(e.children&&Object.keys(e.children).length>0){s.children={};for(const[i,o]of Object.entries(e.children)){const a=r?`${r}${e.delimiter||"."}${i}`:i;s.children[i]=t(o,a,new Set(n))}}return n.delete(e),s},r={};for(const[n,s]of Object.entries(e))r[n]=t(s,n);return{content:[{type:"text",text:JSON.stringify(r,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Failed to list mailboxes"))}}async handleSearchMessages(e){await this.ensureRequiredConnections(!0,!1);let t=e.criteria;t&&Array.isArray(t)&&0!==t.length||(t=["ALL"]);try{console.error("[IMAP] Searching with criteria:",t);const e=await this.imapClient.search(t),r={searchCriteria:t,matchingUIDs:e,totalMatches:e.length,note:1===t.length&&"ALL"===t[0]?'Showing all messages. Use specific criteria like ["UNSEEN"] to filter results.':"Search completed with specified criteria."};return{content:[{type:"text",text:JSON.stringify(r,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Search failed"))}}async handleSearchBySender(e){await this.ensureRequiredConnections(!0,!1);const t=e.sender;if(!t)throw new Error("sender parameter is required");try{const e=["FROM",t];console.error("[IMAP] Searching messages from sender:",t);const r=await this.imapClient.search(e),n={searchType:"By Sender",sender:t,searchCriteria:e,matchingUIDs:r,totalMatches:r.length};return{content:[{type:"text",text:JSON.stringify(n,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Search by sender failed"))}}async handleSearchBySubject(e){await this.ensureRequiredConnections(!0,!1);const t=e.subject;if(!t)throw new Error("subject parameter is required");try{const e=["SUBJECT",t];console.error("[IMAP] Searching messages with subject:",t);const r=await this.imapClient.search(e),n={searchType:"By Subject",subjectKeywords:t,searchCriteria:e,matchingUIDs:r,totalMatches:r.length};return{content:[{type:"text",text:JSON.stringify(n,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Search by subject failed"))}}async handleSearchSinceDate(e){await this.ensureRequiredConnections(!0,!1);const t=e.date;if(!t)throw new Error("date parameter is required");try{const e=["SINCE",t];console.error("[IMAP] Searching messages since date:",t);const r=await this.imapClient.search(e),n={searchType:"Since Date",sinceDate:t,searchCriteria:e,matchingUIDs:r,totalMatches:r.length,note:'Date format should be like "April 20, 2010" or "20-Apr-2010"'};return{content:[{type:"text",text:JSON.stringify(n,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Search since date failed"))}}async handleSearchUnreadFromSender(e){await this.ensureRequiredConnections(!0,!1);const t=e.sender;if(!t)throw new Error("sender parameter is required");try{const e=["UNSEEN",["FROM",t]];console.error("[IMAP] Searching unread messages from sender:",t);const r=await this.imapClient.search(e),n={searchType:"Unread messages from specific sender",sender:t,searchCriteria:e,matchingUIDs:r,totalMatches:r.length,note:"By default, all criteria are ANDed together - finds messages that are BOTH unread AND from the specified sender"};return{content:[{type:"text",text:JSON.stringify(n,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Search unread from sender failed"))}}async handleSearchByBody(e){await this.ensureRequiredConnections(!0,!1);const t=e.text;if(!t)throw new Error("text parameter is required");try{const e=["BODY",t];console.error("[IMAP] Searching messages with body text:",t);const r=await this.imapClient.search(e),n={searchType:"By Body Text",bodyText:t,searchCriteria:e,matchingUIDs:r,totalMatches:r.length};return{content:[{type:"text",text:JSON.stringify(n,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Search by body failed"))}}async handleSearchLargerThan(e){await this.ensureRequiredConnections(!0,!1);const t=e.size;if("number"!=typeof t)throw new Error("size parameter must be a number");try{const e=["LARGER",t.toString()];console.error("[IMAP] Searching messages larger than:",t,"bytes");const r=await this.imapClient.search(e),n={searchType:"Larger Than Size",minimumSize:t,searchCriteria:e,matchingUIDs:r,totalMatches:r.length};return{content:[{type:"text",text:JSON.stringify(n,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Search larger than failed"))}}async handleSearchWithKeyword(e){await this.ensureRequiredConnections(!0,!1);const t=e.keyword;if(!t)throw new Error("keyword parameter is required");try{const e=["KEYWORD",t];console.error("[IMAP] Searching messages with keyword:",t);const r=await this.imapClient.search(e),n={searchType:"With Keyword",keyword:t,searchCriteria:e,matchingUIDs:r,totalMatches:r.length};return{content:[{type:"text",text:JSON.stringify(n,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Search with keyword failed"))}}async handleGetMessages(e){await this.ensureRequiredConnections(!0,!1);const t=e.uids;if(!Array.isArray(t))throw new Error("uids must be an array of numbers");const r=e.markSeen||!1;try{const e=await this.imapClient.fetchMessages(t,{markSeen:r});return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Failed to get messages"))}}async handleGetMessage(e){await this.ensureRequiredConnections(!0,!1);const t=e.uid;if("number"!=typeof t)throw new Error("uid must be a number");e.markSeen;try{const e=await this.imapClient.getMessage(t);return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Failed to get message"))}}async handleDeleteMessage(e){await this.ensureRequiredConnections(!0,!1);const t=e.uid;if("number"!=typeof t)throw new Error("uid must be a number");try{return await this.imapClient.deleteMessage(t),{content:[{type:"text",text:`Message with UID ${t} deleted successfully`}]}}catch(e){throw new Error(this.formatError(e,"Failed to delete message"))}}async handleGetMessageCount(){await this.ensureRequiredConnections(!0,!1);try{return{content:[{type:"text",text:`Total messages: ${await this.imapClient.getMessageCount()}`}]}}catch(e){throw new Error(this.formatError(e,"Failed to get message count"))}}async handleGetUnseenMessages(){await this.ensureRequiredConnections(!0,!1);try{const e=await this.imapClient.getUnseenMessages();return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Failed to get unseen messages"))}}async handleGetRecentMessages(){await this.ensureRequiredConnections(!0,!1);try{const e=await this.imapClient.getRecentMessages();return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Failed to get recent messages"))}}async handleGetConnectionStatus(){const e={timestamp:(new Date).toLocaleString("zh-CN",{timeZone:"Asia/Shanghai",year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit"}),connections:{imap:{connected:!1,currentBox:null,serverInfo:`${d.IMAP.host}:${d.IMAP.port}`,username:d.IMAP.username,tls:d.IMAP.tls,status:"Not connected"},smtp:{connected:!1,serverInfo:`${d.SMTP.host}:${d.SMTP.port}`,username:d.SMTP.username,secure:d.SMTP.secure,status:"Not connected"}}};return this.imapClient&&(e.connections.imap.connected=this.imapClient.isConnected(),e.connections.imap.currentBox=this.imapClient.getCurrentBox(),e.connections.imap.connected?e.connections.imap.status=e.connections.imap.currentBox?`Connected - Current mailbox: ${e.connections.imap.currentBox}`:"Connected - No mailbox open":e.connections.imap.status="Connection lost or failed"),this.smtpClient&&(e.connections.smtp.connected=!0,e.connections.smtp.status="Connected and ready"),{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}async handleSendEmail(e){await this.ensureRequiredConnections(!1,!0);const t={to:e.to.split(",").map(e=>e.trim()),subject:e.subject,text:e.text,html:e.html,cc:e.cc?e.cc.split(",").map(e=>e.trim()):void 0,bcc:e.bcc?e.bcc.split(",").map(e=>e.trim()):void 0};if(!t.text&&!t.html)throw new Error("Either text or html content is required");try{const e=await this.smtpClient.sendMail(t);return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Failed to send email"))}}async handleConnectAll(){const e=[];try{this.imapClient&&this.imapClient.isConnected()?e.push("ℹ️ IMAP: Already connected"):(await this.ensureIMAPConnection(),e.push("✅ IMAP: Connected successfully"))}catch(t){e.push(`❌ IMAP: Connection failed - ${t instanceof Error?t.message:String(t)}`)}try{this.smtpClient?e.push("ℹ️ SMTP: Already connected"):(await this.ensureSMTPConnection(),e.push("✅ SMTP: Connected successfully"))}catch(t){e.push(`❌ SMTP: Connection failed - ${t instanceof Error?t.message:String(t)}`)}return{content:[{type:"text",text:e.join("\n")}]}}async handleDisconnectAll(){const e=[];if(this.imapClient)try{await this.imapClient.disconnect(),this.imapClient=null,e.push("✅ IMAP: Disconnected successfully")}catch(t){e.push(`❌ IMAP: Disconnect failed - ${t instanceof Error?t.message:String(t)}`)}else e.push("ℹ️ IMAP: Not connected");if(this.smtpClient)try{await this.smtpClient.disconnect(),this.smtpClient=null,e.push("✅ SMTP: Disconnected successfully")}catch(t){e.push(`❌ SMTP: Disconnect failed - ${t instanceof Error?t.message:String(t)}`)}else e.push("ℹ️ SMTP: Not connected");return{content:[{type:"text",text:e.join("\n")}]}}validateConfig(){try{console.error("=== MCP Mail Server Configuration ==="),console.error(`IMAP: ${d.IMAP.host}:${d.IMAP.port} (TLS: ${d.IMAP.tls})`),console.error(`SMTP: ${d.SMTP.host}:${d.SMTP.port} (Secure: ${d.SMTP.secure})`),console.error(`User: ${d.IMAP.username}`),console.error("Password: [CONFIGURED]"),console.error("Configuration loaded successfully")}catch(e){throw console.error("Configuration error:",e instanceof Error?e.message:String(e)),console.error("Please ensure all required environment variables are set in your MCP server configuration."),e}}async run(){const e=new t;await this.server.connect(e),console.error("MCP Mail server running on stdio")}}).run().catch(console.error);
2
+ import{Server as e}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as t}from"@modelcontextprotocol/sdk/server/stdio.js";import{ListToolsRequestSchema as r,CallToolRequestSchema as n}from"@modelcontextprotocol/sdk/types.js";import s from"imap";import{EventEmitter as o}from"events";import{simpleParser as i}from"mailparser";import a from"nodemailer";class IMAPClient extends o{imap=null;config;connected=!1;authenticated=!1;currentBox=null;constructor(e){super(),this.config=e}async connect(){return new Promise((e,t)=>{console.error(`[IMAP] Connecting to ${this.config.host}:${this.config.port} (TLS: ${this.config.tls})`);const r={user:this.config.username,password:this.config.password,host:this.config.host,port:this.config.port,tls:this.config.tls||!1,connTimeout:this.config.connTimeout||1e4,authTimeout:this.config.authTimeout||5e3,keepalive:!1!==this.config.keepalive};this.imap=new s(r),this.imap.once("ready",async()=>{console.error("[IMAP] Connection ready"),this.connected=!0,this.authenticated=!0;try{await this.openBox("INBOX",!0),console.error("[IMAP] Auto-opened INBOX")}catch(e){console.error("[IMAP] Failed to auto-open INBOX:",e instanceof Error?e.message:String(e))}e()}),this.imap.once("error",e=>{console.error("[IMAP] Connection error:",e.message),t(new Error(`IMAP connection failed: ${e.message}`))}),this.imap.once("end",()=>{console.error("[IMAP] Connection ended"),this.connected=!1,this.authenticated=!1,this.currentBox=null}),this.imap.connect()})}async openBox(e="INBOX",t=!1){if(!this.imap||!this.authenticated)throw new Error("Not connected or authenticated");return new Promise((r,n)=>{this.imap.openBox(e,t,(t,s)=>{if(t)return console.error(`[IMAP] Failed to open box ${e}:`,t.message),void n(new Error(`Failed to open mailbox: ${t.message}`));console.error(`[IMAP] Opened box ${e}`),this.currentBox=e;const o={name:e,messages:{total:s.messages.total,new:s.messages.new,unseen:s.messages.unseen},permFlags:s.permFlags,uidvalidity:s.uidvalidity,uidnext:s.uidnext};r(o)})})}async getBoxes(){if(!this.imap||!this.authenticated)throw new Error("Not connected or authenticated");return new Promise((e,t)=>{this.imap.getBoxes((r,n)=>{r?t(new Error(`Failed to get boxes: ${r.message}`)):e(n)})})}async search(e=["ALL"]){if(!this.imap)throw new Error("Not connected to IMAP server");return this.currentBox||await this.openBox("INBOX",!0),new Promise((t,r)=>{this.imap.search([e],(e,n)=>{if(e)return console.error("[IMAP] Search failed:",e.message),void r(new Error(`Search failed: ${e.message}`));console.error(`[IMAP] Search found ${n.length} messages`),t(n)})})}async fetchMessages(e,t={}){if(!this.imap)throw new Error("Not connected to IMAP server");this.currentBox||await this.openBox("INBOX",!0);const r={bodies:t.bodies||["HEADER","TEXT"],struct:!1!==t.struct,envelope:!1!==t.envelope,markSeen:t.markSeen||!1,...t};return new Promise((t,n)=>{const s=[],o=new Map;if(0===e.length)return void t(s);const a=this.imap.fetch(e,r);a.on("message",(e,t)=>{console.error(`[IMAP] Processing message ${t}`);let r={},n="";const s=[],i={uid:0,id:t,flags:[],date:"",size:0};e.on("body",(e,t)=>{const o=[];e.on("data",e=>{o.push(e),s.push(e)}),e.once("end",()=>{const e=Buffer.concat(o);if("HEADER"===t.which){const t=e.toString("utf8");r=this.parseHeaders(t)}else"TEXT"===t.which&&(n=e.toString("utf8"))})}),e.once("attributes",e=>{i.uid=e.uid,i.flags=e.flags||[];const t=e.date||new Date;i.date=t.toLocaleString("zh-CN",{timeZone:"Asia/Shanghai",year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit"}),i.size=e.size||0}),e.once("end",()=>{console.error(`[IMAP] Message ${t} processed, preparing for parse`),o.set(t,{message:i,headers:r,body:n,rawBuffer:Buffer.concat(s)})})}),a.once("error",e=>{console.error("[IMAP] Fetch error:",e.message),n(new Error(`Fetch failed: ${e.message}`))}),a.once("end",async()=>{console.error(`[IMAP] Fetch completed, parsing ${o.size} messages`);for(const[e,t]of o)try{const e=await i(t.rawBuffer),r=e=>e?Array.isArray(e)?e.map(e=>n(e)).filter(Boolean).join(", "):n(e):"",n=e=>{if(!e)return"";if("string"==typeof e){const t=e.match(/<([^>]+)>/)||e.match(/([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/);return t?t[1]:e}if(e&&"object"==typeof e){if(e.address)return e.address;if(e.text){const t=e.text.match(/<([^>]+)>/)||e.text.match(/([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/);return t?t[1]:e.text}}return""};s.push({...t.message,subject:e.subject||"No Subject",from:r(e.from),to:r(e.to),cc:r(e.cc)||void 0,bcc:r(e.bcc)||void 0,text:e.text})}catch(r){console.error(`[IMAP] Failed to parse message ${e}:`,r),s.push({...t.message,subject:t.headers.subject||"Parse Failed",from:t.headers.from||"",to:t.headers.to||"",cc:t.headers.cc||void 0,bcc:t.headers.bcc||void 0,text:t.body.trim()})}console.error(`[IMAP] All messages parsed, returning ${s.length} messages`),t(s)})})}async getMessage(e){const t=await this.fetchMessages([e]);if(0===t.length)throw new Error(`Message with UID ${e} not found`);return t[0]}async deleteMessage(e){if(!this.imap)throw new Error("Not connected to IMAP server");return this.currentBox||await this.openBox("INBOX",!1),new Promise((t,r)=>{this.imap.addFlags(e,["\\Deleted"],n=>{if(n)return console.error(`[IMAP] Failed to mark message ${e} as deleted:`,n.message),void r(new Error(`Failed to delete message: ${n.message}`));console.error(`[IMAP] Message ${e} marked for deletion`),this.imap.expunge(n=>{if(n)return console.error("[IMAP] Failed to expunge:",n.message),void r(new Error(`Failed to expunge deleted messages: ${n.message}`));console.error(`[IMAP] Message ${e} deleted successfully`),t()})})})}async getMessageCount(){return this.currentBox||await this.openBox("INBOX",!0),(await this.search(["ALL"])).length}async getUnseenMessages(){const e=await this.search(["UNSEEN"]);return this.fetchMessages(e)}async getRecentMessages(){const e=await this.search(["RECENT"]);return this.fetchMessages(e)}parseHeaders(e){const t={},r=e.split("\r\n");let n="",s="";for(const e of r)if(e.match(/^\s/)&&n)s+=" "+e.trim();else{n&&(t[n.toLowerCase()]=s.trim());const r=e.indexOf(":");r>-1?(n=e.substring(0,r).trim(),s=e.substring(r+1).trim()):(n="",s="")}return n&&(t[n.toLowerCase()]=s.trim()),t}async disconnect(){if(this.imap)return this.connected?new Promise((e,t)=>{const r=setTimeout(()=>{console.error("[IMAP] Disconnect timeout, forcing cleanup"),this.connected=!1,this.authenticated=!1,this.currentBox=null,this.imap=null,e()},5e3);this.imap.once("end",()=>{clearTimeout(r),console.error("[IMAP] Disconnected"),this.connected=!1,this.authenticated=!1,this.currentBox=null,this.imap=null,e()}),this.imap.once("error",t=>{clearTimeout(r),console.error("[IMAP] Disconnect error:",t.message),this.connected=!1,this.authenticated=!1,this.currentBox=null,this.imap=null,e()});try{this.imap.end()}catch(t){clearTimeout(r),console.error("[IMAP] Error calling end():",t),this.connected=!1,this.authenticated=!1,this.currentBox=null,this.imap=null,e()}}):(this.imap=null,this.authenticated=!1,void(this.currentBox=null))}isConnected(){return this.connected&&this.authenticated}getCurrentBox(){return this.currentBox}}class SMTPClient{transporter=null;config;constructor(e){this.config=e}async connect(){this.transporter=a.createTransport({host:this.config.host,port:this.config.port,secure:this.config.secure||!1,auth:{user:this.config.username,pass:this.config.password}});try{this.transporter&&await this.transporter.verify()}catch(e){throw new Error(`SMTP connection failed: ${e instanceof Error?e.message:String(e)}`)}}async sendMail(e){if(!this.transporter)throw new Error("SMTP client not connected");const t={from:e.from||this.config.username,to:Array.isArray(e.to)?e.to.join(", "):e.to,cc:e.cc?Array.isArray(e.cc)?e.cc.join(", "):e.cc:void 0,bcc:e.bcc?Array.isArray(e.bcc)?e.bcc.join(", "):e.bcc:void 0,subject:e.subject,text:e.text,html:e.html,attachments:e.attachments};try{const e=await this.transporter.sendMail(t);return{messageId:e.messageId,response:e.response,accepted:e.accepted||[],rejected:e.rejected||[]}}catch(e){throw new Error(`Failed to send email: ${e instanceof Error?e.message:String(e)}`)}}async disconnect(){if(this.transporter)try{this.transporter.close(),console.error("[SMTP] Disconnected successfully")}catch(e){console.error("[SMTP] Error during disconnect:",e instanceof Error?e.message:String(e))}finally{this.transporter=null}}}function c(e,t){const r=process.env[e];if(!r)throw new Error(`Missing required environment variable: ${e}. Please set this variable in your MCP server configuration.`);return r}function l(e){const t=process.env[e];if(!t)throw new Error(`Missing required environment variable: ${e}. Please set this variable to 'true' or 'false' in your MCP server configuration.`);if("true"!==t.toLowerCase()&&"false"!==t.toLowerCase())throw new Error(`Invalid boolean value for environment variable ${e}: ${t}. Must be 'true' or 'false'.`);return"true"===t.toLowerCase()}function d(e){const t=process.env[e];if(!t)throw new Error(`Missing required environment variable: ${e}. Please set this variable to a valid number in your MCP server configuration.`);const r=parseInt(t,10);if(isNaN(r))throw new Error(`Invalid number value for environment variable ${e}: ${t}. Must be a valid number.`);return r}const h={IMAP:{host:c("IMAP_HOST"),port:d("IMAP_PORT"),username:c("EMAIL_USER"),password:c("EMAIL_PASS"),tls:l("IMAP_SECURE")},SMTP:{host:c("SMTP_HOST"),port:d("SMTP_PORT"),username:c("EMAIL_USER"),password:c("EMAIL_PASS"),secure:l("SMTP_SECURE")}};(new class{server;imapClient=null;smtpClient=null;isInitializing=!1;formatError(e,t){return`${t}: ${e instanceof Error?e.message:String(e)}`}filterMessagesByDateRange(e,t,r){if(!t&&!r)return e;const n=t?new Date(t):null,s=r?new Date(r):null;return e.filter(e=>{if(!e.date)return!0;const t=new Date(e.date);return!(!isNaN(t.getTime())&&(n&&t<n||s&&t>s))})}async searchInMultipleMailboxes(e,t,r,n="",s=""){const o=["INBOX"],i=["INBOX.Sent","Sent","SENT","Sent Items","Sent Messages","已发送"];let a=!1;for(const e of i)try{await this.imapClient.openBox(e,!0),o.push(e),a=!0;break}catch(e){continue}const c={searchType:t,searchValue:r,searchCriteria:e,mailboxesSearched:[],totalMatches:0,messages:[]};for(const t of o)try{console.error(`[IMAP] Searching in mailbox: ${t}`),await this.imapClient.openBox(t,!0);const r=await this.imapClient.search(e);console.error(`[IMAP] Found ${r.length} messages in ${t}`);let o=[],i=[];if(r.length>0){console.error(`[IMAP] Auto-fetching content for ${r.length} messages from ${t}`);let e=(await this.imapClient.fetchMessages(r)).map(e=>({...e,sourceMailbox:t}));(n||s)&&(e=this.filterMessagesByDateRange(e,n,s)),o=e,i=e.map(e=>e.uid),c.messages.push(...o)}const a={mailbox:t,matchingUIDs:i,messageCount:o.length};c.mailboxesSearched.push(a)}catch(e){console.error(`[IMAP] Error searching in ${t}:`,e),c.mailboxesSearched.push({mailbox:t,error:`Failed to search: ${e instanceof Error?e.message:String(e)}`,matchingUIDs:[],messageCount:0})}if(c.totalMatches=c.messages.length,c.messages.sort((e,t)=>{const r=new Date(e.date||0);return new Date(t.date||0).getTime()-r.getTime()}),c.totalMatches>0){let e=`Found and retrieved ${c.totalMatches} messages across ${c.mailboxesSearched.length} mailboxes`;(n||s)&&(e+=" (filtered by date range)"),c.note=e}else c.note="No messages found in any of the searched mailboxes";return a||(c.warning="Could not find sent mailbox - only searched INBOX"),c}constructor(){this.validateConfig(),this.server=new e({name:"mcp-mail",version:"1.0.0"},{capabilities:{tools:{}}}),this.setupToolHandlers(),this.setupErrorHandling()}setupErrorHandling(){this.server.onerror=e=>console.error("[MCP Error]",e),process.on("SIGINT",async()=>{this.imapClient&&await this.imapClient.disconnect(),this.smtpClient&&await this.smtpClient.disconnect(),await this.server.close(),process.exit(0)})}setupToolHandlers(){this.server.setRequestHandler(r,async()=>({tools:[{name:"connect_all",description:"Connect to both IMAP and SMTP servers simultaneously",inputSchema:{type:"object",properties:{}}},{name:"list_mailboxes",description:"List all available mailboxes (folders). Auto-connects if not already connected.",inputSchema:{type:"object",properties:{}}},{name:"open_mailbox",description:"Open a specific mailbox (folder) and optionally retrieve sent mailbox info. Due to IMAP protocol limitations, only one mailbox stays open. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{mailboxName:{type:"string",description:"Name of the mailbox to open (default: INBOX)",default:"INBOX"},readOnly:{type:"boolean",description:"Open mailbox in read-only mode (default: false)",default:!1},openSent:{type:"boolean",description:"Also retrieve sent mailbox information (default: true)",default:!0}}}},{name:"get_message_count",description:"Get the total number of messages in current mailbox. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{}}},{name:"get_unseen_messages",description:"Get all unseen (unread) messages. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{}}},{name:"get_recent_messages",description:"Get all recent messages. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{}}},{name:"search_by_sender",description:"Search messages from a specific sender with optional date range. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{sender:{type:"string",description:"Email address of the sender to search for"},startDate:{type:"string",description:'Optional start date for filtering (format: "2025-07-01" or "01-Jul-2025"). Leave empty to not filter by start date.'},endDate:{type:"string",description:'Optional end date for filtering (format: "2025-08-01" or "01-Aug-2025"). Leave empty to not filter by end date.'}},required:["sender"]}},{name:"search_by_subject",description:"Search messages by subject keywords with optional date range. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{subject:{type:"string",description:"Keywords to search in email subject"},startDate:{type:"string",description:'Optional start date for filtering (format: "2025-07-01" or "01-Jul-2025"). Leave empty to not filter by start date.'},endDate:{type:"string",description:'Optional end date for filtering (format: "2025-08-01" or "01-Aug-2025"). Leave empty to not filter by end date.'}},required:["subject"]}},{name:"search_by_recipient",description:"Search messages sent to a specific recipient email address with optional date range. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{recipient:{type:"string",description:"Email address of the recipient to search for"},startDate:{type:"string",description:'Optional start date for filtering (format: "2025-07-01" or "01-Jul-2025"). Leave empty to not filter by start date.'},endDate:{type:"string",description:'Optional end date for filtering (format: "2025-08-01" or "01-Aug-2025"). Leave empty to not filter by end date.'}},required:["recipient"]}},{name:"search_since_date",description:"Search messages from a specific date until now (not for date ranges). Use search_messages for complex date ranges.",inputSchema:{type:"object",properties:{date:{type:"string",description:'Start date to search from (searches from this date to present). Formats: "April 20, 2010", "20-Apr-2010", or "2010-04-20"'}},required:["date"]}},{name:"search_unread_from_sender",description:"Search unread messages from a specific sender with optional date range (demonstrates AND logic). Auto-connects if not already connected.",inputSchema:{type:"object",properties:{sender:{type:"string",description:"Email address of the sender"},startDate:{type:"string",description:'Optional start date for filtering (format: "2025-07-01" or "01-Jul-2025"). Leave empty to not filter by start date.'},endDate:{type:"string",description:'Optional end date for filtering (format: "2025-08-01" or "01-Aug-2025"). Leave empty to not filter by end date.'}},required:["sender"]}},{name:"search_by_body",description:"Search messages containing specific text in the body with optional date range. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{text:{type:"string",description:"Text to search for in message body"},startDate:{type:"string",description:'Optional start date for filtering (format: "2025-07-01" or "01-Jul-2025"). Leave empty to not filter by start date.'},endDate:{type:"string",description:'Optional end date for filtering (format: "2025-08-01" or "01-Aug-2025"). Leave empty to not filter by end date.'}},required:["text"]}},{name:"search_with_keyword",description:"Search messages with specific keyword/flag with optional date range. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{keyword:{type:"string",description:"Keyword to search for"},startDate:{type:"string",description:'Optional start date for filtering (format: "2025-07-01" or "01-Jul-2025"). Leave empty to not filter by start date.'},endDate:{type:"string",description:'Optional end date for filtering (format: "2025-08-01" or "01-Aug-2025"). Leave empty to not filter by end date.'}},required:["keyword"]}},{name:"get_messages",description:"Retrieve multiple messages by their UIDs. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{uids:{type:"array",description:"Array of message UIDs to retrieve",items:{type:"number"}},markSeen:{type:"boolean",description:"Mark messages as seen when retrieving (default: false)",default:!1}},required:["uids"]}},{name:"get_message",description:"Retrieve a specific email message by UID. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{uid:{type:"number",description:"Message UID to retrieve"},markSeen:{type:"boolean",description:"Mark message as seen when retrieving (default: false)",default:!1}},required:["uid"]}},{name:"send_email",description:"Send an email via SMTP. Auto-connects to SMTP server if not already connected.",inputSchema:{type:"object",properties:{to:{type:"string",description:"Recipient email address(es), comma-separated"},subject:{type:"string",description:"Email subject"},text:{type:"string",description:"Plain text email body"},html:{type:"string",description:"HTML email body (optional)"},cc:{type:"string",description:"CC recipients, comma-separated (optional)"},bcc:{type:"string",description:"BCC recipients, comma-separated (optional)"}},required:["to","subject"]}},{name:"delete_message",description:"Delete a specific email message by UID. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{uid:{type:"number",description:"Message UID to delete"}},required:["uid"]}},{name:"get_connection_status",description:"Check the current connection status of both IMAP and SMTP servers.",inputSchema:{type:"object",properties:{}}},{name:"disconnect_all",description:"Disconnect from both IMAP and SMTP servers. Only disconnects if currently connected.",inputSchema:{type:"object",properties:{}}}]})),this.server.setRequestHandler(n,async e=>{const{name:t,arguments:r}=e.params;try{switch(t){case"open_mailbox":return await this.handleOpenMailbox(r);case"list_mailboxes":return await this.handleListMailboxes();case"search_by_sender":return await this.handleSearchBySender(r);case"search_by_subject":return await this.handleSearchBySubject(r);case"search_by_recipient":return await this.handleSearchByRecipient(r);case"search_since_date":return await this.handleSearchSinceDate(r);case"search_unread_from_sender":return await this.handleSearchUnreadFromSender(r);case"search_by_body":return await this.handleSearchByBody(r);case"search_with_keyword":return await this.handleSearchWithKeyword(r);case"get_messages":return await this.handleGetMessages(r);case"get_message":return await this.handleGetMessage(r);case"delete_message":return await this.handleDeleteMessage(r);case"get_message_count":return await this.handleGetMessageCount();case"get_unseen_messages":return await this.handleGetUnseenMessages();case"get_recent_messages":return await this.handleGetRecentMessages();case"get_connection_status":return await this.handleGetConnectionStatus();case"send_email":return await this.handleSendEmail(r);case"connect_all":return await this.handleConnectAll();case"disconnect_all":return await this.handleDisconnectAll();default:throw new Error(`Unknown tool: ${t}`)}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:String(e)}`}]}}})}async ensureIMAPConnection(){if(!this.imapClient||!this.imapClient.isConnected())if(this.isInitializing)for(;this.isInitializing;)await new Promise(e=>setTimeout(e,100));else{this.isInitializing=!0;try{const e=h.IMAP;console.error(`[IMAP] Auto-connecting to ${e.host}:${e.port}`),this.imapClient=new IMAPClient(e),await this.imapClient.connect(),console.error("[IMAP] Auto-connection successful")}finally{this.isInitializing=!1}}}async ensureSMTPConnection(){if(this.smtpClient)return;const e=h.SMTP;console.error(`[SMTP] Auto-connecting to ${e.host}:${e.port}`),this.smtpClient=new SMTPClient(e),await this.smtpClient.connect(),console.error("[SMTP] Auto-connection successful")}async ensureRequiredConnections(e=!1,t=!1){e&&await this.ensureIMAPConnection(),t&&await this.ensureSMTPConnection()}async handleOpenMailbox(e){await this.ensureRequiredConnections(!0,!1);const t=e.mailboxName||"INBOX",r=e.readOnly||!1,n=!1!==e.openSent;try{const e={},s=await this.imapClient.openBox(t,r);if(e[t]=s,e.currentlyOpen=t,n&&"INBOX.Sent"!==t&&"Sent"!==t&&"SENT"!==t)try{const n=["INBOX.Sent","Sent","SENT","Sent Items","Sent Messages","已发送"];let s=!1;for(const o of n)try{const n=await this.imapClient.openBox(o,!0);e[o]=n,s=!0,await this.imapClient.openBox(t,r),e.currentlyOpen=t,e.note=`Retrieved info from both ${t} and ${o}. Currently open: ${t}`;break}catch(e){continue}s||(e.sentBoxWarning="Could not find any sent mailbox")}catch(t){e.sentBoxError=`Failed to access sent mailbox: ${t instanceof Error?t.message:String(t)}`}return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Failed to open mailbox"))}}async handleListMailboxes(){await this.ensureRequiredConnections(!0,!1);try{const e=await this.imapClient.getBoxes(),t=(e,r="",n=new Set)=>{if(n.has(e))return{name:r,circular:!0};n.add(e);const s={name:r,attribs:e.attribs||[],delimiter:e.delimiter||".",selectable:!e.attribs?.includes("\\Noselect")};if(e.children&&Object.keys(e.children).length>0){s.children={};for(const[o,i]of Object.entries(e.children)){const a=r?`${r}${e.delimiter||"."}${o}`:o;s.children[o]=t(i,a,new Set(n))}}return n.delete(e),s},r={};for(const[n,s]of Object.entries(e))r[n]=t(s,n);return{content:[{type:"text",text:JSON.stringify(r,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Failed to list mailboxes"))}}async handleSearchBySender(e){await this.ensureRequiredConnections(!0,!1);const t=e.sender,r=e.startDate||"",n=e.endDate||"";if(!t)throw new Error("sender parameter is required");try{const e=["FROM",t];console.error("[IMAP] Searching messages from sender across all mailboxes:",t);const s=await this.searchInMultipleMailboxes(e,"By Sender",t,r,n);return s.sender=t,r&&(s.startDate=r),n&&(s.endDate=n),{content:[{type:"text",text:JSON.stringify(s,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Search by sender failed"))}}async handleSearchBySubject(e){await this.ensureRequiredConnections(!0,!1);const t=e.subject,r=e.startDate||"",n=e.endDate||"";if(!t)throw new Error("subject parameter is required");try{const e=["SUBJECT",t];console.error("[IMAP] Searching messages with subject across all mailboxes:",t);const s=await this.searchInMultipleMailboxes(e,"By Subject",t,r,n);return s.subjectKeywords=t,r&&(s.startDate=r),n&&(s.endDate=n),{content:[{type:"text",text:JSON.stringify(s,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Search by subject failed"))}}async handleSearchByRecipient(e){await this.ensureRequiredConnections(!0,!1);const t=e.recipient,r=e.startDate||"",n=e.endDate||"";if(!t)throw new Error("recipient parameter is required");try{const e=["TO",t];console.error("[IMAP] Searching messages to recipient across all mailboxes:",t);const s=await this.searchInMultipleMailboxes(e,"By Recipient",t,r,n);return s.recipient=t,r&&(s.startDate=r),n&&(s.endDate=n),{content:[{type:"text",text:JSON.stringify(s,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Search by recipient failed"))}}async handleSearchSinceDate(e){await this.ensureRequiredConnections(!0,!1);const t=e.date;if(!t)throw new Error("date parameter is required");try{const e=["SINCE",t];console.error("[IMAP] Searching messages since date across all mailboxes:",t);const r=await this.searchInMultipleMailboxes(e,"Since Date",t);return r.sinceDate=t,r.note='Date format should be like "April 20, 2010" or "20-Apr-2010". Searched across multiple mailboxes.',{content:[{type:"text",text:JSON.stringify(r,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Search since date failed"))}}async handleSearchUnreadFromSender(e){await this.ensureRequiredConnections(!0,!1);const t=e.sender,r=e.startDate||"",n=e.endDate||"";if(!t)throw new Error("sender parameter is required");try{const e=["UNSEEN",["FROM",t]];console.error("[IMAP] Searching unread messages from sender across all mailboxes:",t);const s=await this.searchInMultipleMailboxes(e,"Unread messages from specific sender",t,r,n);return s.sender=t,r&&(s.startDate=r),n&&(s.endDate=n),s.note="By default, all criteria are ANDed together - finds messages that are BOTH unread AND from the specified sender. Searched across multiple mailboxes.",{content:[{type:"text",text:JSON.stringify(s,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Search unread from sender failed"))}}async handleSearchByBody(e){await this.ensureRequiredConnections(!0,!1);const t=e.text,r=e.startDate||"",n=e.endDate||"";if(!t)throw new Error("text parameter is required");try{const e=["BODY",t];console.error("[IMAP] Searching messages with body text across all mailboxes:",t);const s=await this.searchInMultipleMailboxes(e,"By Body Text",t,r,n);return s.bodyText=t,r&&(s.startDate=r),n&&(s.endDate=n),{content:[{type:"text",text:JSON.stringify(s,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Search by body failed"))}}async handleSearchWithKeyword(e){await this.ensureRequiredConnections(!0,!1);const t=e.keyword,r=e.startDate||"",n=e.endDate||"";if(!t)throw new Error("keyword parameter is required");try{const e=["KEYWORD",t];console.error("[IMAP] Searching messages with keyword across all mailboxes:",t);const s=await this.searchInMultipleMailboxes(e,"With Keyword",t,r,n);return s.keyword=t,r&&(s.startDate=r),n&&(s.endDate=n),{content:[{type:"text",text:JSON.stringify(s,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Search with keyword failed"))}}async handleGetMessages(e){await this.ensureRequiredConnections(!0,!1);const t=e.uids;if(!Array.isArray(t))throw new Error("uids must be an array of numbers");const r=e.markSeen||!1;try{const e=await this.imapClient.fetchMessages(t,{markSeen:r});return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Failed to get messages"))}}async handleGetMessage(e){await this.ensureRequiredConnections(!0,!1);const t=e.uid;if("number"!=typeof t)throw new Error("uid must be a number");try{const e=await this.imapClient.getMessage(t);return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Failed to get message"))}}async handleDeleteMessage(e){await this.ensureRequiredConnections(!0,!1);const t=e.uid;if("number"!=typeof t)throw new Error("uid must be a number");try{return await this.imapClient.deleteMessage(t),{content:[{type:"text",text:`Message with UID ${t} deleted successfully`}]}}catch(e){throw new Error(this.formatError(e,"Failed to delete message"))}}async handleGetMessageCount(){await this.ensureRequiredConnections(!0,!1);try{return{content:[{type:"text",text:`Total messages: ${await this.imapClient.getMessageCount()}`}]}}catch(e){throw new Error(this.formatError(e,"Failed to get message count"))}}async handleGetUnseenMessages(){await this.ensureRequiredConnections(!0,!1);try{const e=await this.imapClient.getUnseenMessages();return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Failed to get unseen messages"))}}async handleGetRecentMessages(){await this.ensureRequiredConnections(!0,!1);try{const e=await this.imapClient.getRecentMessages();return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Failed to get recent messages"))}}async handleGetConnectionStatus(){const e={timestamp:(new Date).toLocaleString("zh-CN",{timeZone:"Asia/Shanghai",year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit"}),connections:{imap:{connected:!1,currentBox:null,serverInfo:`${h.IMAP.host}:${h.IMAP.port}`,username:h.IMAP.username,tls:h.IMAP.tls,status:"Not connected"},smtp:{connected:!1,serverInfo:`${h.SMTP.host}:${h.SMTP.port}`,username:h.SMTP.username,secure:h.SMTP.secure,status:"Not connected"}}};return this.imapClient&&(e.connections.imap.connected=this.imapClient.isConnected(),e.connections.imap.currentBox=this.imapClient.getCurrentBox(),e.connections.imap.connected?e.connections.imap.status=e.connections.imap.currentBox?`Connected - Current mailbox: ${e.connections.imap.currentBox}`:"Connected - No mailbox open":e.connections.imap.status="Connection lost or failed"),this.smtpClient&&(e.connections.smtp.connected=!0,e.connections.smtp.status="Connected and ready"),{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}async handleSendEmail(e){await this.ensureRequiredConnections(!1,!0);const t={to:e.to.split(",").map(e=>e.trim()),subject:e.subject,text:e.text,html:e.html,cc:e.cc?e.cc.split(",").map(e=>e.trim()):void 0,bcc:e.bcc?e.bcc.split(",").map(e=>e.trim()):void 0};if(!t.text&&!t.html)throw new Error("Either text or html content is required");try{const e=await this.smtpClient.sendMail(t);return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Failed to send email"))}}async handleConnectAll(){const e=[];try{this.imapClient&&this.imapClient.isConnected()?e.push("ℹ️ IMAP: Already connected"):(await this.ensureIMAPConnection(),e.push("✅ IMAP: Connected successfully"))}catch(t){e.push(`❌ IMAP: Connection failed - ${t instanceof Error?t.message:String(t)}`)}try{this.smtpClient?e.push("ℹ️ SMTP: Already connected"):(await this.ensureSMTPConnection(),e.push("✅ SMTP: Connected successfully"))}catch(t){e.push(`❌ SMTP: Connection failed - ${t instanceof Error?t.message:String(t)}`)}return{content:[{type:"text",text:e.join("\n")}]}}async handleDisconnectAll(){const e=[];if(this.imapClient)try{await this.imapClient.disconnect(),this.imapClient=null,e.push("✅ IMAP: Disconnected successfully")}catch(t){e.push(`❌ IMAP: Disconnect failed - ${t instanceof Error?t.message:String(t)}`)}else e.push("ℹ️ IMAP: Not connected");if(this.smtpClient)try{await this.smtpClient.disconnect(),this.smtpClient=null,e.push("✅ SMTP: Disconnected successfully")}catch(t){e.push(`❌ SMTP: Disconnect failed - ${t instanceof Error?t.message:String(t)}`)}else e.push("ℹ️ SMTP: Not connected");return{content:[{type:"text",text:e.join("\n")}]}}validateConfig(){try{console.error("=== MCP Mail Server Configuration ==="),console.error(`IMAP: ${h.IMAP.host}:${h.IMAP.port} (TLS: ${h.IMAP.tls})`),console.error(`SMTP: ${h.SMTP.host}:${h.SMTP.port} (Secure: ${h.SMTP.secure})`),console.error(`User: ${h.IMAP.username}`),console.error("Password: [CONFIGURED]"),console.error("Configuration loaded successfully")}catch(e){throw console.error("Configuration error:",e instanceof Error?e.message:String(e)),console.error("Please ensure all required environment variables are set in your MCP server configuration."),e}}async run(){const e=new t;await this.server.connect(e),console.error("MCP Mail server running on stdio")}}).run().catch(console.error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-mail-server",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "MCP server for IMAP/SMTP email access with environment-based configuration",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -36,7 +36,7 @@
36
36
  "license": "MIT",
37
37
  "repository": {
38
38
  "type": "git",
39
- "url": "https://github.com/yunfeizhu/mcp-mail-server.git"
39
+ "url": "git+https://github.com/yunfeizhu/mcp-mail-server.git"
40
40
  },
41
41
  "homepage": "https://github.com/yunfeizhu/mcp-mail-server#readme",
42
42
  "bugs": {