mcp-mail-server 1.0.1 → 1.1.1

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 +172 -52
  2. package/dist/index.js +1 -1
  3. package/package.json +7 -3
package/README.md CHANGED
@@ -2,18 +2,22 @@
2
2
 
3
3
  **Language:** English | [中文](README-zh.md)
4
4
 
5
- A Model Context Protocol (MCP) server that enables email operations through POP3 and SMTP protocols in Cursor AI. Features secure environment-based configuration for seamless email management.
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
6
 
7
7
  ### ✨ Features
8
8
 
9
- #### 📥 POP3 Features (Receive Emails)
10
- - Connect to POP3 email servers
11
- - User authentication with TLS support
12
- - List all messages in mailbox
13
- - Retrieve specific email content
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
14
15
  - Delete messages from server
15
- - Get total message count
16
- - Secure connection management
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
17
21
 
18
22
  #### 📤 SMTP Features (Send Emails)
19
23
  - Connect to SMTP email servers
@@ -47,9 +51,9 @@ Add the following configuration to your Cursor MCP settings:
47
51
  "command": "npx",
48
52
  "args": ["mcp-mail-server"],
49
53
  "env": {
50
- "POP3_HOST": "your-pop3-server.com",
51
- "POP3_PORT": "995",
52
- "POP3_SECURE": "true",
54
+ "IMAP_HOST": "your-imap-server.com",
55
+ "IMAP_PORT": "993",
56
+ "IMAP_SECURE": "true",
53
57
  "SMTP_HOST": "your-smtp-server.com",
54
58
  "SMTP_PORT": "465",
55
59
  "SMTP_SECURE": "true",
@@ -68,9 +72,9 @@ Add the following configuration to your Cursor MCP settings:
68
72
  "mcp-mail-server": {
69
73
  "command": "mcp-mail-server",
70
74
  "env": {
71
- "POP3_HOST": "your-pop3-server.com",
72
- "POP3_PORT": "995",
73
- "POP3_SECURE": "true",
75
+ "IMAP_HOST": "your-imap-server.com",
76
+ "IMAP_PORT": "993",
77
+ "IMAP_SECURE": "true",
74
78
  "SMTP_HOST": "your-smtp-server.com",
75
79
  "SMTP_PORT": "465",
76
80
  "SMTP_SECURE": "true",
@@ -84,32 +88,110 @@ Add the following configuration to your Cursor MCP settings:
84
88
 
85
89
  ### 🛠️ Available Tools
86
90
 
87
- #### `connect_pop3`
88
- Connect to POP3 email server using preconfigured settings.
91
+ #### Connection Management
89
92
 
90
- #### `list_messages`
91
- List all messages in the mailbox with metadata.
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.
113
+
114
+ #### Email Search Tools
115
+
116
+ #### `search_messages`
117
+ Search messages using IMAP search criteria.
118
+
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)
92
171
 
93
172
  #### `get_message`
94
- Retrieve complete content of a specific email.
173
+ Retrieve complete content of a specific email by UID.
95
174
 
96
175
  **Parameters:**
97
- - `messageId` (number): Message ID to retrieve
176
+ - `uid` (number): Message UID to retrieve
177
+ - `markSeen` (boolean, optional): Mark message as seen when retrieving (default: false)
98
178
 
99
179
  #### `delete_message`
100
- Delete a specific email message.
180
+ Delete a specific email message by UID.
101
181
 
102
182
  **Parameters:**
103
- - `messageId` (number): Message ID to delete
183
+ - `uid` (number): Message UID to delete
104
184
 
105
185
  #### `get_message_count`
106
- Get the total number of messages in the mailbox.
186
+ Get the total number of messages in current mailbox.
187
+
188
+ #### `get_unseen_messages`
189
+ Get all unseen (unread) messages in current mailbox.
107
190
 
108
- #### `disconnect`
109
- Disconnect from the POP3 server.
191
+ #### `get_recent_messages`
192
+ Get all recent messages in current mailbox.
110
193
 
111
- #### `connect_smtp`
112
- Connect to SMTP email server using preconfigured settings.
194
+ #### Email Sending
113
195
 
114
196
  #### `send_email`
115
197
  Send an email via SMTP.
@@ -122,11 +204,6 @@ Send an email via SMTP.
122
204
  - `cc` (string, optional): CC recipients, comma-separated
123
205
  - `bcc` (string, optional): BCC recipients, comma-separated
124
206
 
125
- #### `disconnect_smtp`
126
- Disconnect from the SMTP server.
127
-
128
- #### `quick_connect`
129
- Connect to both POP3 and SMTP servers simultaneously using preconfigured settings.
130
207
 
131
208
  ### 💡 Usage Examples
132
209
 
@@ -142,48 +219,91 @@ Connect to email servers
142
219
  ```
143
220
  or
144
221
  ```
145
- Quick connect to mail
222
+ Connect to all mail servers
223
+ ```
224
+
225
+ 2. Check connection status:
226
+ ```
227
+ Show email connection status
228
+ ```
229
+ or
230
+ ```
231
+ Check mail server connections
146
232
  ```
147
233
 
148
234
  #### 📥 Receiving Emails
149
235
 
150
- 1. Connect to POP3 server (if not using quick connect):
236
+ **Note: IMAP connection is established automatically when needed**
237
+
238
+ 1. Open a mailbox:
239
+ ```
240
+ Open INBOX mailbox
241
+ ```
242
+ or
151
243
  ```
152
- Connect to POP3 server
244
+ Open Sent mailbox in read-only mode
153
245
  ```
154
246
 
155
- 2. List emails:
247
+ 2. Search for emails:
248
+ ```
249
+ Search for unseen messages
250
+ ```
251
+ or
252
+ ```
253
+ Search for messages from sender@example.com
156
254
  ```
157
- Show me my email list
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
158
266
  ```
159
267
 
160
- 3. Get specific email:
268
+ 3. Get specific email by UID:
161
269
  ```
162
- Show me the content of email ID 1
270
+ Show me the content of email with UID 123
163
271
  ```
164
272
 
165
- 4. Delete email:
273
+ 4. Get all unseen messages:
166
274
  ```
167
- Delete email with ID 1
275
+ Show me all unread emails
168
276
  ```
169
277
 
170
- #### 📤 Sending Emails
278
+ 5. List available mailboxes:
279
+ ```
280
+ Show me all email folders
281
+ ```
171
282
 
172
- 1. Connect to SMTP server (if not using quick connect):
283
+ 6. Delete email by UID:
173
284
  ```
174
- Connect to SMTP server
285
+ Delete email with UID 123
175
286
  ```
176
287
 
177
- 2. Send simple email:
288
+ #### 📤 Sending Emails
289
+
290
+ **Note: SMTP connection is established automatically when needed**
291
+
292
+ 1. Send simple email:
178
293
  ```
179
294
  Send email to recipient@example.com with subject "Test Email" and message "Hello, this is a test email"
180
295
  ```
181
296
 
182
- 3. Send HTML email:
297
+ 2. Send HTML email:
183
298
  ```
184
299
  Send HTML email to recipient@example.com with subject "Welcome" and HTML content "<h1>Welcome!</h1><p>This is an HTML email</p>"
185
300
  ```
186
301
 
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
+ ```
306
+
187
307
  ### 🔧 Environment Configuration
188
308
 
189
309
  #### Required Environment Variables
@@ -192,9 +312,9 @@ Send HTML email to recipient@example.com with subject "Welcome" and HTML content
192
312
 
193
313
  | Variable | Description | Example Value |
194
314
  |----------|-------------|---------------|
195
- | `POP3_HOST` | POP3 server address | your-pop3-server.com |
196
- | `POP3_PORT` | POP3 port number | 995 |
197
- | `POP3_SECURE` | Enable TLS (true/false) | true |
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 |
198
318
  | `SMTP_HOST` | SMTP server address | your-smtp-server.com |
199
319
  | `SMTP_PORT` | SMTP port number | 465 |
200
320
  | `SMTP_SECURE` | Enable SSL (true/false) | true |
@@ -238,9 +358,9 @@ npm run build
238
358
  4. Local testing:
239
359
  ```bash
240
360
  # Set environment variables
241
- export POP3_HOST=your-pop3-server.com
242
- export POP3_PORT=995
243
- export POP3_SECURE=true
361
+ export IMAP_HOST=your-imap-server.com
362
+ export IMAP_PORT=993
363
+ export IMAP_SECURE=true
244
364
  export SMTP_HOST=your-smtp-server.com
245
365
  export SMTP_PORT=465
246
366
  export SMTP_SECURE=true
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 n,CallToolRequestSchema as r}from"@modelcontextprotocol/sdk/types.js";import s from"net";import o from"tls";import{EventEmitter as i}from"events";import c from"nodemailer";class POP3Client extends i{socket=null;config;connected=!1;authenticated=!1;buffer="";constructor(e){super(),this.config=e}async connect(){return new Promise((e,t)=>{console.error(`[POP3] Connecting to ${this.config.host}:${this.config.port} (TLS: ${this.config.tls})`);const n=setTimeout(()=>{this.socket&&this.socket.destroy(),t(new Error(`Connection timeout to ${this.config.host}:${this.config.port}`))},1e4);this.config.tls?this.socket=o.connect({host:this.config.host,port:this.config.port,rejectUnauthorized:!1}):this.socket=s.createConnection(this.config.port,this.config.host),this.socket.on("connect",()=>{console.error("[POP3] Socket connected, waiting for server greeting..."),this.connected=!0}),this.socket.on("secureConnect",()=>{console.error("[POP3] TLS connection established, waiting for server greeting..."),this.connected=!0}),this.socket.on("data",r=>{console.error("[POP3] Received data:",r.toString().trim()),this.buffer+=r.toString(),this.processBuffer(e,t,n)}),this.socket.on("error",e=>{console.error("[POP3] Connection error:",e.message),clearTimeout(n),t(new Error(`POP3 connection failed: ${e.message}`))}),this.socket.on("close",()=>{console.error("[POP3] Connection closed"),clearTimeout(n),this.connected=!1,this.authenticated=!1})})}processBuffer(e,t,n){const r=this.buffer.split("\r\n");if(r.length>1){const s=r[0];this.buffer=r.slice(1).join("\r\n"),console.error("[POP3] Server response:",s),s.startsWith("+OK")?(n&&clearTimeout(n),e&&(console.error("[POP3] Connection successful!"),e())):s.startsWith("-ERR")&&(n&&clearTimeout(n),t&&(console.error("[POP3] Server error:",s),t(new Error(`POP3 server error: ${s}`))))}}async sendCommand(e){return new Promise((t,n)=>{if(!this.socket||!this.connected)return void n(new Error("Not connected"));this.socket.write(e+"\r\n");const r=e=>{const s=e.toString();this.socket?.removeListener("data",r),s.startsWith("+OK")?t(s):n(new Error(s))};this.socket.on("data",r)})}async authenticate(){if(!this.connected)throw new Error("Not connected to server");try{console.error("[POP3] Authenticating user:",this.config.username),await this.sendCommand(`USER ${this.config.username}`),console.error("[POP3] Sending password..."),await this.sendCommand(`PASS ${this.config.password}`),this.authenticated=!0,console.error("[POP3] Authentication successful!")}catch(e){throw console.error("[POP3] Authentication failed:",e),new Error(`Authentication failed: ${e instanceof Error?e.message:String(e)}`)}}async getMessageCount(){if(!this.authenticated)throw new Error("Not authenticated");const e=(await this.sendCommand("STAT")).match(/\+OK (\d+)/);return e?parseInt(e[1]):0}async listMessages(){if(!this.authenticated)throw new Error("Not authenticated");return new Promise((e,t)=>{if(!this.socket)return void t(new Error("Not connected"));this.socket.write("LIST\r\n");let n="";const r=t=>{if(n+=t.toString(),n.includes("\r\n.\r\n")){this.socket?.removeListener("data",r);const t=n.split("\r\n"),s=[];for(let e=1;e<t.length-2;e++){const n=t[e].split(" ");n.length>=2&&s.push({id:parseInt(n[0]),size:parseInt(n[1])})}e(s)}};this.socket.on("data",r)})}async retrieveMessage(e){if(!this.authenticated)throw new Error("Not authenticated");return new Promise((t,n)=>{if(!this.socket)return void n(new Error("Not connected"));this.socket.write(`RETR ${e}\r\n`);let r="";const s=n=>{if(r+=n.toString(),r.includes("\r\n.\r\n")){this.socket?.removeListener("data",s);const n=r.substring(r.indexOf("\r\n")+2,r.lastIndexOf("\r\n.\r\n")),o=this.parseMessage(e,n);t(o)}};this.socket.on("data",s)})}parseMessage(e,t){const n=t.indexOf("\r\n\r\n"),r=n>-1?t.substring(0,n):"",s=n>-1?t.substring(n+4):t,o={},i=r.split("\r\n");for(const e of i){const t=e.indexOf(":");if(t>-1){const n=e.substring(0,t).trim().toLowerCase(),r=e.substring(t+1).trim();o[n]=r}}return{id:e,size:t.length,headers:o,body:s.trim(),raw:t}}async deleteMessage(e){if(!this.authenticated)throw new Error("Not authenticated");await this.sendCommand(`DELE ${e}`)}async quit(){this.socket&&this.connected&&(await this.sendCommand("QUIT"),this.socket.end())}disconnect(){this.socket&&(this.socket.destroy(),this.connected=!1,this.authenticated=!1)}}class SMTPClient{transporter=null;config;constructor(e){this.config=e}async connect(){this.transporter=c.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(){this.transporter&&(this.transporter.close(),this.transporter=null)}}function a(e){const t=process.env[e];if(!t)throw new Error(`Missing required environment variable: ${e}. Please set this variable in your MCP server configuration.`);return t}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 n=parseInt(t,10);if(isNaN(n))throw new Error(`Invalid number value for environment variable ${e}: ${t}. Must be a valid number.`);return n}const p={POP3:{host:a("POP3_HOST"),port:l("POP3_PORT"),username:a("EMAIL_USER"),password:a("EMAIL_PASS"),tls:h("POP3_SECURE")},SMTP:{host:a("SMTP_HOST"),port:l("SMTP_PORT"),username:a("EMAIL_USER"),password:a("EMAIL_PASS"),secure:h("SMTP_SECURE")}};(new class{server;pop3Client=null;smtpClient=null;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.pop3Client&&this.pop3Client.disconnect(),this.smtpClient&&await this.smtpClient.disconnect(),await this.server.close(),process.exit(0)})}setupToolHandlers(){this.server.setRequestHandler(n,async()=>({tools:[{name:"connect_pop3",description:"Connect to POP3 email server (using preconfigured settings)",inputSchema:{type:"object",properties:{}}},{name:"list_messages",description:"List all messages in the mailbox",inputSchema:{type:"object",properties:{}}},{name:"get_message",description:"Retrieve a specific email message",inputSchema:{type:"object",properties:{messageId:{type:"number",description:"Message ID to retrieve"}},required:["messageId"]}},{name:"delete_message",description:"Delete a specific email message",inputSchema:{type:"object",properties:{messageId:{type:"number",description:"Message ID to delete"}},required:["messageId"]}},{name:"get_message_count",description:"Get the total number of messages in the mailbox",inputSchema:{type:"object",properties:{}}},{name:"disconnect",description:"Disconnect from the POP3 server",inputSchema:{type:"object",properties:{}}},{name:"connect_smtp",description:"Connect to SMTP email server for sending emails (using preconfigured settings)",inputSchema:{type:"object",properties:{}}},{name:"send_email",description:"Send an email via SMTP",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:"disconnect_smtp",description:"Disconnect from the SMTP server",inputSchema:{type:"object",properties:{}}},{name:"quick_connect",description:"Connect to both POP3 and SMTP servers at once (using preconfigured settings)",inputSchema:{type:"object",properties:{}}}]})),this.server.setRequestHandler(r,async e=>{const{name:t,arguments:n}=e.params;try{switch(t){case"connect_pop3":return await this.handleConnect();case"list_messages":return await this.handleListMessages();case"get_message":return await this.handleGetMessage(n);case"delete_message":return await this.handleDeleteMessage(n);case"get_message_count":return await this.handleGetMessageCount();case"disconnect":return await this.handleDisconnect();case"connect_smtp":return await this.handleConnectSMTP();case"send_email":return await this.handleSendEmail(n);case"disconnect_smtp":return await this.handleDisconnectSMTP();case"quick_connect":return await this.handleQuickConnect();default:throw new Error(`Unknown tool: ${t}`)}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:String(e)}`}]}}})}async handleConnect(){const e=p.POP3;try{return this.pop3Client=new POP3Client(e),await this.pop3Client.connect(),await this.pop3Client.authenticate(),{content:[{type:"text",text:`Successfully connected to POP3 server ${e.host}:${e.port}`}]}}catch(e){throw new Error(`Failed to connect: ${e instanceof Error?e.message:String(e)}`)}}async handleListMessages(){if(!this.pop3Client)throw new Error("Not connected to POP3 server");const e=await this.pop3Client.listMessages();return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}async handleGetMessage(e){if(!this.pop3Client)throw new Error("Not connected to POP3 server");const t=e.messageId;if("number"!=typeof t)throw new Error("messageId must be a number");const n=await this.pop3Client.retrieveMessage(t);return{content:[{type:"text",text:JSON.stringify(n,null,2)}]}}async handleDeleteMessage(e){if(!this.pop3Client)throw new Error("Not connected to POP3 server");const t=e.messageId;if("number"!=typeof t)throw new Error("messageId must be a number");return await this.pop3Client.deleteMessage(t),{content:[{type:"text",text:`Message ${t} marked for deletion`}]}}async handleGetMessageCount(){if(!this.pop3Client)throw new Error("Not connected to POP3 server");return{content:[{type:"text",text:`Total messages: ${await this.pop3Client.getMessageCount()}`}]}}async handleDisconnect(){if(!this.pop3Client)throw new Error("Not connected to POP3 server");return await this.pop3Client.quit(),this.pop3Client=null,{content:[{type:"text",text:"Disconnected from POP3 server"}]}}async handleConnectSMTP(){const e=p.SMTP;try{return this.smtpClient=new SMTPClient(e),await this.smtpClient.connect(),{content:[{type:"text",text:`Successfully connected to SMTP server ${e.host}:${e.port}`}]}}catch(e){throw new Error(`Failed to connect to SMTP server: ${e instanceof Error?e.message:String(e)}`)}}async handleSendEmail(e){if(!this.smtpClient)throw new Error("Not connected to SMTP server");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(`Failed to send email: ${e instanceof Error?e.message:String(e)}`)}}async handleDisconnectSMTP(){if(!this.smtpClient)throw new Error("Not connected to SMTP server");return await this.smtpClient.disconnect(),this.smtpClient=null,{content:[{type:"text",text:"Disconnected from SMTP server"}]}}async handleQuickConnect(){const e=[];let t=!1,n=!1;try{const n=p.POP3;this.pop3Client=new POP3Client(n),await this.pop3Client.connect(),await this.pop3Client.authenticate(),e.push(`✅ POP3: Successfully connected to ${n.host}:${n.port}`),t=!0}catch(t){e.push(`❌ POP3: Failed to connect - ${t instanceof Error?t.message:String(t)}`)}try{const t=p.SMTP;this.smtpClient=new SMTPClient(t),await this.smtpClient.connect(),e.push(`✅ SMTP: Successfully connected to ${t.host}:${t.port}`),n=!0}catch(t){e.push(`❌ SMTP: Failed to connect - ${t instanceof Error?t.message:String(t)}`)}const r=`Connection Summary: POP3 ${t?"✅":"❌"} | SMTP ${n?"✅":"❌"}`;return e.push("",r),{content:[{type:"text",text:e.join("\n")}]}}validateConfig(){try{console.error("=== MCP Mail Server Configuration ==="),console.error(`POP3: ${p.POP3.host}:${p.POP3.port} (TLS: ${p.POP3.tls})`),console.error(`SMTP: ${p.SMTP.host}:${p.SMTP.port} (Secure: ${p.SMTP.secure})`),console.error(`User: ${p.POP3.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 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);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mcp-mail-server",
3
- "version": "1.0.1",
4
- "description": "MCP server for POP3/SMTP email access with environment-based configuration",
3
+ "version": "1.1.1",
4
+ "description": "MCP server for IMAP/SMTP email access with environment-based configuration",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
7
7
  "bin": {
@@ -23,7 +23,7 @@
23
23
  ],
24
24
  "keywords": [
25
25
  "mcp",
26
- "pop3",
26
+ "imap",
27
27
  "smtp",
28
28
  "email",
29
29
  "mail",
@@ -47,6 +47,8 @@
47
47
  },
48
48
  "dependencies": {
49
49
  "@modelcontextprotocol/sdk": "^0.5.0",
50
+ "imap": "^0.8.19",
51
+ "mailparser": "^3.7.4",
50
52
  "nodemailer": "^6.9.0"
51
53
  },
52
54
  "devDependencies": {
@@ -54,6 +56,8 @@
54
56
  "@rollup/plugin-node-resolve": "^15.0.0",
55
57
  "@rollup/plugin-terser": "^0.4.0",
56
58
  "@rollup/plugin-typescript": "^11.0.0",
59
+ "@types/imap": "^0.8.42",
60
+ "@types/mailparser": "^3.4.6",
57
61
  "@types/node": "^20.0.0",
58
62
  "@types/nodemailer": "^6.4.0",
59
63
  "rollup": "^4.0.0",