mcp-mail-server 1.2.0 → 1.2.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.
package/README.md CHANGED
@@ -17,6 +17,37 @@ A Model Context Protocol server for IMAP/SMTP email operations with Claude, Curs
17
17
  - **Auto Connection Management**: Automatic IMAP/SMTP connection handling
18
18
  - **Multi-Mailbox Support**: Access INBOX, Sent, and custom folders
19
19
 
20
+ ## Changelog
21
+
22
+ ### [1.2.1] - 2026-03-18
23
+
24
+ **Fixed**
25
+ - Fixed search criteria (FROM/TO/SUBJECT/BODY/KEYWORD/SINCE) not using nested array format, causing errors on TO and other searches
26
+ - Fixed `search()` wrapping criteria in an extra array, breaking compound search conditions
27
+ - Fixed `deleteMessage()` failing silently when the mailbox was opened in read-only mode
28
+ - Fixed `getRecentMessages()` misusing the IMAP `RECENT` flag; now fetches latest N messages by UID
29
+ - Fixed `getRecentMessages()` / `getUnseenMessages()` relying on leftover mailbox state from previous operations
30
+ - Fixed `cleanReplySubject()` only stripping one `Re:` prefix layer, causing false negatives in unreplied detection
31
+ - Fixed email date stored as locale string causing inconsistent `new Date()` parsing across platforms; changed to ISO 8601
32
+ - Fixed `ensureIMAPConnection()` having no timeout while waiting for concurrent initialization
33
+ - Fixed `saveSentMessage()` always returning `sentFolderSaved: true` even when save failed
34
+ - Fixed `handleGetMessages()` / `handleDeleteMessage()` relying on `currentBox` state to locate messages
35
+ - Fixed `reply_to_email` writing literal `"undefined"` into the body when `text` is empty
36
+
37
+ **Added**
38
+ - All search tools now support an `inboxOnly` parameter to restrict search to INBOX only
39
+
40
+ **Improved**
41
+ - `ensureSMTPConnection()` now has concurrency guard with 30-second timeout, consistent with IMAP
42
+ - Sent mailbox auto-detected via RFC 6154 `\Sent` special-use attribute with result caching, compatible with all mail providers
43
+ - `saveMessageToFolder()` simplified; skips saving if no sent folder is found
44
+ - Search now uses `slice(-limit)` to fetch the newest messages first, preventing empty results after date filtering
45
+ - HTML-escape applied to quoted content in reply emails to prevent XSS injection
46
+
47
+ For the full version history, see [CHANGELOG.md](CHANGELOG.md).
48
+
49
+ ---
50
+
20
51
  ## Quick Start
21
52
 
22
53
  1. **Install**: `npm install -g mcp-mail-server`
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{mkdir as s,access as o,writeFile as a,readFile as i}from"fs/promises";import c from"path";import l from"imap";import{EventEmitter as d}from"events";import{simpleParser as h}from"mailparser";import m from"nodemailer";class IMAPClient extends d{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 n={user:this.config.username,password:this.config.password,host:this.config.host,port:this.config.port,tls:this.config.tls||!1,tlsOptions:{rejectUnauthorized:!1,servername:this.config.host},connTimeout:this.config.connTimeout||6e4,authTimeout:this.config.authTimeout||3e4,keepalive:!1!==this.config.keepalive};this.imap=new l(n),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((n,r)=>{this.imap.openBox(e,t,(t,s)=>{if(t)return console.error(`[IMAP] Failed to open box ${e}:`,t.message),void r(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};n(o)})})}async getBoxes(){if(!this.imap||!this.authenticated)throw new Error("Not connected or authenticated");return new Promise((e,t)=>{this.imap.getBoxes((n,r)=>{n?t(new Error(`Failed to get boxes: ${n.message}`)):e(r)})})}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,n)=>{this.imap.search([e],(e,r)=>{if(e)return console.error("[IMAP] Search failed:",e.message),void n(new Error(`Search failed: ${e.message}`));console.error(`[IMAP] Search found ${r.length} messages`),t(r)})})}async fetchMessages(e,t={}){if(!this.imap)throw new Error("Not connected to IMAP server");this.currentBox||await this.openBox("INBOX",!0);const n={bodies:t.bodies||["HEADER","TEXT"],struct:!1!==t.struct,envelope:!1!==t.envelope,markSeen:t.markSeen||!1,...t};return new Promise((t,r)=>{const s=[],o=new Map;if(0===e.length)return void t(s);const a=this.imap.fetch(e,n);a.on("message",(e,t)=>{console.error(`[IMAP] Processing message ${t}`);let n={},r="";const s=[],a={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");n=this.parseHeaders(t)}else"TEXT"===t.which&&(r=e.toString("utf8"))})}),e.once("attributes",e=>{a.uid=e.uid,a.flags=e.flags||[];const t=e.date||new Date;a.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"}),a.size=e.size||0}),e.once("end",()=>{console.error(`[IMAP] Message ${t} processed, preparing for parse`),o.set(t,{message:a,headers:n,body:r,rawBuffer:Buffer.concat(s)})})}),a.once("error",e=>{console.error("[IMAP] Fetch error:",e.message),r(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 h(t.rawBuffer),n=e=>e?Array.isArray(e)?e.map(e=>r(e)).filter(Boolean).join(", "):r(e):"",r=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""},o=(e.attachments||[]).map((e,t)=>({index:t,filename:e.filename||`attachment_${t+1}${e.contentType&&"."+e.contentType.split("/")[1]?.split(";")[0]||""}`,contentType:e.contentType||"application/octet-stream",size:e.size||(e.content?e.content.length:0),contentId:e.contentId||void 0,contentDisposition:e.contentDisposition||void 0}));s.push({...t.message,subject:e.subject||"No Subject",from:n(e.from),to:n(e.to),cc:n(e.cc)||void 0,bcc:n(e.bcc)||void 0,text:e.text,html:e.html,attachments:o.length>0?o:void 0})}catch(n){console.error(`[IMAP] Failed to parse message ${e}:`,n),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 fetchMessageAttachments(e){if(!this.imap)throw new Error("Not connected to IMAP server");return this.currentBox||await this.openBox("INBOX",!0),new Promise((t,n)=>{const r=[],s=this.imap.fetch([e],{bodies:["HEADER","TEXT"],struct:!0,markSeen:!1});s.on("message",e=>{e.on("body",e=>{e.on("data",e=>{r.push(e)})})}),s.once("error",e=>{n(new Error(`Fetch attachments failed: ${e.message}`))}),s.once("end",async()=>{try{const e=Buffer.concat(r),n=((await h(e)).attachments||[]).map((e,t)=>({index:t,filename:e.filename||`attachment_${t+1}${e.contentType&&"."+e.contentType.split("/")[1]?.split(";")[0]||""}`,contentType:e.contentType||"application/octet-stream",size:e.size||(e.content?e.content.length:0),contentId:e.contentId||void 0,contentDisposition:e.contentDisposition||void 0,content:e.content}));t(n)}catch(e){n(new Error(`Failed to parse attachments: ${e instanceof Error?e.message:String(e)}`))}})})}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,n)=>{this.imap.addFlags(e,["\\Deleted"],r=>{if(r)return console.error(`[IMAP] Failed to mark message ${e} as deleted:`,r.message),void n(new Error(`Failed to delete message: ${r.message}`));console.error(`[IMAP] Message ${e} marked for deletion`),this.imap.expunge(r=>{if(r)return console.error("[IMAP] Failed to expunge:",r.message),void n(new Error(`Failed to expunge deleted messages: ${r.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={},n=e.split("\r\n");let r="",s="";for(const e of n)if(e.match(/^\s/)&&r)s+=" "+e.trim();else{r&&(t[r.toLowerCase()]=s.trim());const n=e.indexOf(":");n>-1?(r=e.substring(0,n).trim(),s=e.substring(n+1).trim()):(r="",s="")}return r&&(t[r.toLowerCase()]=s.trim()),t}async disconnect(){if(this.imap)return this.connected?new Promise(e=>{const t=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(t),console.error("[IMAP] Disconnected"),this.connected=!1,this.authenticated=!1,this.currentBox=null,this.imap=null,e()}),this.imap.once("error",n=>{clearTimeout(t),console.error("[IMAP] Disconnect error:",n.message),this.connected=!1,this.authenticated=!1,this.currentBox=null,this.imap=null,e()});try{this.imap.end()}catch(n){clearTimeout(t),console.error("[IMAP] Error calling end():",n),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}getCurrentUsername(){return this.config?.username||null}async saveMessageToFolder(e,t="INBOX.Sent"){if(!this.connected)throw new Error("IMAP client is not connected");return new Promise((n,r)=>{this.imap.openBox(t,!1,s=>{s?(console.warn(`[IMAP] Folder ${t} not found, trying to create it`),this.imap.addBox(t,s=>{if(s)return console.error(`[IMAP] Failed to create folder ${t}:`,s.message),void this.trySaveToAlternateSentFolders(e,n,r);this.saveToOpenedFolder(e,t,n,r)})):this.saveToOpenedFolder(e,t,n,r)})})}saveToOpenedFolder(e,t,n,r){this.imap.append(e,{mailbox:t},e=>{e?(console.error(`[IMAP] Failed to save message to ${t}:`,e.message),r(new Error(`Failed to save message to ${t}: ${e.message}`))):n()})}trySaveToAlternateSentFolders(e,t,n){const r=["Sent","SENT","Sent Items","Sent Messages","已发送"];let s=0;const o=()=>{if(s>=r.length)return console.warn("[IMAP] All sent folder attempts failed, message not saved to sent folder"),void t();const a=r[s++];this.imap.openBox(a,!1,r=>{r?o():this.saveToOpenedFolder(e,a,t,n)})};o()}}class SMTPClient{transporter=null;config;constructor(e){this.config=e}async connect(){this.transporter=m.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)}`)}}getCurrentUsername(){return this.config?.username||null}isConnected(){return null!==this.transporter}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 u(e,t){const n=process.env[e];if(!n)throw new Error(`Missing required environment variable: ${e}. Please set this variable in your MCP server configuration.`);return n}function p(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 g(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 f={IMAP:{host:u("IMAP_HOST"),port:g("IMAP_PORT"),username:u("EMAIL_USER"),password:u("EMAIL_PASS"),tls:p("IMAP_SECURE")},SMTP:{host:u("SMTP_HOST"),port:g("SMTP_PORT"),username:u("EMAIL_USER"),password:u("EMAIL_PASS"),secure:p("SMTP_SECURE")}},y=["INBOX.Sent","Sent","SENT","Sent Items","Sent Messages","已发送"],w=["INBOX",...y];(new class{server;imapClient=null;smtpClient=null;isInitializing=!1;formatError(e,t){return`${t}: ${e instanceof Error?e.message:String(e)}`}isDateOnly(e){return[/^\d{4}-\d{2}-\d{2}$/,/^\d{2}-\w{3}-\d{4}$/,/^\w{3}\s+\d{1,2},?\s+\d{4}$/].some(t=>t.test(e.trim()))}filterMessagesByDateRange(e,t,n){if(!t&&!n)return e;let r=null,s=null;t&&(r=new Date(t),isNaN(r.getTime())?(console.error(`[Filter] Invalid start date format: ${t}, skipping start date filter`),r=null):console.error(`[Filter] Start date parsed as: ${r.toISOString()}`)),n&&(s=new Date(n),isNaN(s.getTime())?(console.error(`[Filter] Invalid end date format: ${n}, skipping end date filter`),s=null):this.isDateOnly(n)?(s.setHours(23,59,59,999),console.error(`[Filter] End date adjusted to end of day: ${s.toISOString()}`)):console.error(`[Filter] End date parsed as: ${s.toISOString()}`));const o=e.length,a=e.filter(e=>{if(!e.date)return!0;const t=new Date(e.date);return!(!isNaN(t.getTime())&&(r&&t<r||s&&t>s))});return console.error(`[Filter] Date filtering: ${o} -> ${a.length} messages`),a}async searchInMultipleMailboxes(e,t,n,r="",s="",o){const a=["INBOX"];let i=!1;for(const e of y)try{await this.imapClient.openBox(e,!0),a.push(e),i=!0;break}catch(t){console.error(`[IMAP] Failed to open sent mailbox ${e}: ${t instanceof Error?t.message:String(t)}`)}const c={searchType:t,searchValue:n,searchCriteria:e,mailboxesSearched:[],totalMatches:0,messages:[]};for(const t of a)try{console.error(`[IMAP] Searching in mailbox: ${t}`),await this.imapClient.openBox(t,!0);const n=await this.imapClient.search(e);console.error(`[IMAP] Found ${n.length} messages in ${t}`);let a=[],i=[];if(n.length>0){const e=o?n.slice(0,o):n;console.error(`[IMAP] Auto-fetching content for ${e.length} messages from ${t}${o?` (limited from ${n.length})`:""}`);let l=(await this.imapClient.fetchMessages(e)).map(e=>({...e,sourceMailbox:t}));(r||s)&&(l=this.filterMessagesByDateRange(l,r,s)),a=l,i=l.map(e=>e.uid),c.messages.push(...a)}const l={mailbox:t,matchingUIDs:i,messageCount:a.length};c.mailboxesSearched.push(l)}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 n=new Date(e.date||0);return new Date(t.date||0).getTime()-n.getTime()}),c.totalMatches>0){let e=`Found and retrieved ${c.totalMatches} messages across ${c.mailboxesSearched.length} mailboxes`;(r||s)&&(e+=" (filtered by date range)"),c.note=e}else c.note="No messages found in any of the searched mailboxes";return i||(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(n,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/time for filtering. Supports multiple formats: "2025-07-01", "2025-07-01 14:30:00", "01-Jul-2025", or ISO format. Leave empty to not filter by start date.'},endDate:{type:"string",description:'Optional end date/time for filtering. Supports multiple formats: "2025-08-01", "2025-08-01 23:59:59", "01-Aug-2025", or ISO format. 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/time for filtering. Supports multiple formats: "2025-07-01", "2025-07-01 14:30:00", "01-Jul-2025", or ISO format. Leave empty to not filter by start date.'},endDate:{type:"string",description:'Optional end date/time for filtering. Supports multiple formats: "2025-08-01", "2025-08-01 23:59:59", "01-Aug-2025", or ISO format. 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/time for filtering. Supports multiple formats: "2025-07-01", "2025-07-01 14:30:00", "01-Jul-2025", or ISO format. Leave empty to not filter by start date.'},endDate:{type:"string",description:'Optional end date/time for filtering. Supports multiple formats: "2025-08-01", "2025-08-01 23:59:59", "01-Aug-2025", or ISO format. 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/time for filtering. Supports multiple formats: "2025-07-01", "2025-07-01 14:30:00", "01-Jul-2025", or ISO format. Leave empty to not filter by start date.'},endDate:{type:"string",description:'Optional end date/time for filtering. Supports multiple formats: "2025-08-01", "2025-08-01 23:59:59", "01-Aug-2025", or ISO format. Leave empty to not filter by end date.'}},required:["sender"]}},{name:"search_unreplied_from_sender",description:"Search unreplied messages from a specific sender with optional date range. Identifies messages that have not been replied to by checking for corresponding reply messages. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{sender:{type:"string",description:"Email address of the sender to search for unreplied messages"},startDate:{type:"string",description:'Optional start date/time for filtering. Supports multiple formats: "2025-07-01", "2025-07-01 14:30:00", "01-Jul-2025", or ISO format. Leave empty to not filter by start date.'},endDate:{type:"string",description:'Optional end date/time for filtering. Supports multiple formats: "2025-08-01", "2025-08-01 23:59:59", "01-Aug-2025", or ISO format. Leave empty to not filter by end date.'},limit:{type:"number",description:"Maximum number of messages to process from each search (default: 10, maximum: 200). Since unreplied emails are typically few, smaller limits are recommended.",default:10}},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/time for filtering. Supports multiple formats: "2025-07-01", "2025-07-01 14:30:00", "01-Jul-2025", or ISO format. Leave empty to not filter by start date.'},endDate:{type:"string",description:'Optional end date/time for filtering. Supports multiple formats: "2025-08-01", "2025-08-01 23:59:59", "01-Aug-2025", or ISO format. 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/time for filtering. Supports multiple formats: "2025-07-01", "2025-07-01 14:30:00", "01-Jul-2025", or ISO format. Leave empty to not filter by start date.'},endDate:{type:"string",description:'Optional end date/time for filtering. Supports multiple formats: "2025-08-01", "2025-08-01 23:59:59", "01-Aug-2025", or ISO format. Leave empty to not filter by end date.'}},required:["keyword"]}},{name:"search_all_messages",description:"Search all messages across mailboxes with optional date range and limit. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{startDate:{type:"string",description:'Optional start date/time for filtering. Supports multiple formats: "2025-07-01", "2025-07-01 14:30:00", "01-Jul-2025", or ISO format. Leave empty to not filter by start date.'},endDate:{type:"string",description:'Optional end date/time for filtering. Supports multiple formats: "2025-08-01", "2025-08-01 23:59:59", "01-Aug-2025", or ISO format. Leave empty to not filter by end date.'},limit:{type:"number",description:"Maximum number of messages to return (default: 50). Use a smaller value for faster results.",default:50}},required:[]}},{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)"},attachments:{type:"array",description:"Array of absolute file paths to attach (optional)",items:{type:"string"}}},required:["to","subject"]}},{name:"reply_to_email",description:"Reply to a specific email by UID. Automatically sets reply headers, adds Re: prefix, and includes original message. Auto-connects to both IMAP and SMTP if not already connected.",inputSchema:{type:"object",properties:{originalUid:{type:"number",description:"UID of the original message to reply to"},text:{type:"string",description:"Reply message text"},html:{type:"string",description:"Reply message HTML (optional)"},replyToAll:{type:"boolean",description:"Reply to all recipients instead of just sender (default: false)",default:!1},includeOriginal:{type:"boolean",description:"Include original message in reply (default: true)",default:!0}},required:["originalUid"]}},{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_attachments",description:"Get attachment metadata (filename, size, contentType, index) for a specific email by UID. Does not download attachment content. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{uid:{type:"number",description:"Message UID to get attachments for"}},required:["uid"]}},{name:"save_attachment",description:"Download and save email attachments to local file system. Can save a single attachment by index or all attachments. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{uid:{type:"number",description:"Message UID containing the attachment(s)"},savePath:{type:"string",description:"Absolute path to the directory where attachments will be saved"},attachmentIndex:{type:"number",description:"Index of the specific attachment to save (0-based). If omitted, all attachments will be saved."},returnBase64:{type:"boolean",description:"Whether to also return base64-encoded content in the response (default: false)",default:!1}},required:["uid","savePath"]}},{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(r,async e=>{const{name:t,arguments:n}=e.params;try{switch(t){case"open_mailbox":return await this.handleOpenMailbox(n||{});case"list_mailboxes":return await this.handleListMailboxes();case"search_by_sender":return await this.handleSearchBySender(n||{});case"search_by_subject":return await this.handleSearchBySubject(n);case"search_by_recipient":return await this.handleSearchByRecipient(n);case"search_since_date":return await this.handleSearchSinceDate(n);case"search_unread_from_sender":return await this.handleSearchUnreadFromSender(n);case"search_unreplied_from_sender":return await this.handleSearchUnrepliedFromSender(n);case"search_by_body":return await this.handleSearchByBody(n);case"search_with_keyword":return await this.handleSearchWithKeyword(n);case"search_all_messages":return await this.handleSearchAllMessages(n);case"get_messages":return await this.handleGetMessages(n);case"get_message":return await this.handleGetMessage(n);case"delete_message":return await this.handleDeleteMessage(n);case"get_attachments":return await this.handleGetAttachments(n);case"save_attachment":return await this.handleSaveAttachment(n);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(n);case"reply_to_email":return await this.handleReplyToEmail(n);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=f.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=f.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",n=e.readOnly||!1,r=!1!==e.openSent;try{const e={},s=await this.imapClient.openBox(t,n);if(e[t]=s,e.currentlyOpen=t,r&&"INBOX.Sent"!==t&&"Sent"!==t&&"SENT"!==t)try{const r=["INBOX.Sent","Sent","SENT","Sent Items","Sent Messages","已发送"];let s=!1;for(const o of r)try{const r=await this.imapClient.openBox(o,!0);e[o]=r,s=!0,await this.imapClient.openBox(t,n),e.currentlyOpen=t,e.note=`Retrieved info from both ${t} and ${o}. Currently open: ${t}`;break}catch(e){console.error(`[IMAP] Failed to open sent mailbox ${o}: ${e instanceof Error?e.message:String(e)}`)}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,n="",r=new Set)=>{if(r.has(e))return{name:n,circular:!0};r.add(e);const s={name:n,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,a]of Object.entries(e.children)){const i=n?`${n}${e.delimiter||"."}${o}`:o;s.children[o]=t(a,i,new Set(r))}}return r.delete(e),s},n={};for(const[r,s]of Object.entries(e))n[r]=t(s,r);return{content:[{type:"text",text:JSON.stringify(n,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,n=e.startDate||"",r=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,n,r);return s.sender=t,n&&(s.startDate=n),r&&(s.endDate=r),{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,n=e.startDate||"",r=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,n,r);return s.subjectKeywords=t,n&&(s.startDate=n),r&&(s.endDate=r),{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,n=e.startDate||"",r=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,n,r);return s.recipient=t,n&&(s.startDate=n),r&&(s.endDate=r),{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 n=await this.searchInMultipleMailboxes(e,"Since Date",t);return n.sinceDate=t,n.note='Date format should be like "April 20, 2010" or "20-Apr-2010". Searched across multiple mailboxes.',{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,n=e.startDate||"",r=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,n,r);return s.sender=t,n&&(s.startDate=n),r&&(s.endDate=r),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 handleSearchUnrepliedFromSender(e){await this.ensureRequiredConnections(!0,!1);const t=e.sender,n=e.startDate||"",r=e.endDate||"",s=Math.min(Math.max(e.limit||10,1),200);if(!t)throw new Error("sender parameter is required");try{console.error(`[IMAP] Searching unreplied messages from sender: ${t}`);const e=await this.searchInMultipleMailboxes(["FROM",t],"From Sender",t,n,r,s),o=await this.searchInMultipleMailboxes(["TO",t],"To Sender",t,n,r,s);console.error(`[IMAP] Found ${e.messages.length} messages from sender, ${o.messages.length} messages to sender`),e.messages.length>s&&(console.error(`[IMAP] Warning: Found ${e.messages.length} messages from sender, processing only the latest ${s}`),e.messages=e.messages.sort((e,t)=>new Date(t.date).getTime()-new Date(e.date).getTime()).slice(0,s));const a=this.detectUnrepliedMessagesAdvanced(e.messages,o.messages,t,s);console.error(`[IMAP] Found ${a.length} unreplied messages using advanced detection`);const i={searchType:"Unreplied messages from sender (Advanced)",searchValue:t,searchCriteria:["FROM",t],mailboxesSearched:e.mailboxesSearched,totalMatches:a.length,messages:a,sender:t,note:`Found ${a.length} unreplied messages from ${t} using advanced thread-aware detection.${s<200?` (limited to ${s} messages per search)`:""}`};return n&&(i.startDate=n),r&&(i.endDate=r),(n||r)&&(i.note+=" (filtered by date range)"),{content:[{type:"text",text:JSON.stringify(i,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Search unreplied from sender failed"))}}async handleSearchByBody(e){await this.ensureRequiredConnections(!0,!1);const t=e.text,n=e.startDate||"",r=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,n,r);return s.bodyText=t,n&&(s.startDate=n),r&&(s.endDate=r),{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,n=e.startDate||"",r=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,n,r);return s.keyword=t,n&&(s.startDate=n),r&&(s.endDate=r),{content:[{type:"text",text:JSON.stringify(s,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Search with keyword failed"))}}async handleSearchAllMessages(e){await this.ensureRequiredConnections(!0,!1);const t=e.startDate||"",n=e.endDate||"",r=e.limit||50;try{const e=["ALL"];console.error(`[IMAP] Searching all messages across mailboxes, limit: ${r}`);const s=await this.searchInMultipleMailboxes(e,"All Messages","*",t,n,r);return t&&(s.startDate=t),n&&(s.endDate=n),{content:[{type:"text",text:JSON.stringify(s,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Search all messages 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 n=e.markSeen||!1;try{let e=[],r=new Set;if(this.imapClient.getCurrentBox())try{const s=await this.imapClient.fetchMessages(t,{markSeen:n});e.push(...s),s.forEach(e=>r.add(e.uid))}catch(e){console.error(`[GetMessages] Failed to fetch from current mailbox: ${e instanceof Error?e.message:String(e)}`)}const s=t.filter(e=>!r.has(e));if(s.length>0)for(const t of w){if(0===s.length)break;try{await this.imapClient.openBox(t,!0);const o=await this.imapClient.fetchMessages(s,{markSeen:n});o.length>0&&(e.push(...o),o.forEach(e=>{r.add(e.uid);const t=s.indexOf(e.uid);t>-1&&s.splice(t,1)}),o.length)}catch(e){console.error(`[GetMessages] Failed to search in ${t}: ${e instanceof Error?e.message:String(e)}`)}}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");const n=e.markSeen||!1;try{const e=await this.findMessageInMultipleMailboxes(t);if(!e)throw new Error(`Message with UID ${t} not found in any mailbox`);if(n)try{const e=await this.imapClient.fetchMessages([t],{markSeen:!0});if(e.length>0)return{content:[{type:"text",text:JSON.stringify(e[0],null,2)}]}}catch(e){console.error(`[GetMessage] Failed to mark message as seen: ${e instanceof Error?e.message:String(e)}`)}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 handleGetAttachments(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.findMessageInMultipleMailboxes(t);if(!e)throw new Error(`Message with UID ${t} not found in any mailbox`);const n=e.attachments||[],r={uid:t,subject:e.subject,from:e.from,attachmentCount:n.length,attachments:n.map(e=>({index:e.index,filename:e.filename,contentType:e.contentType,size:e.size,contentId:e.contentId,contentDisposition:e.contentDisposition})),note:n.length>0?`Found ${n.length} attachment(s). Use save_attachment tool with uid=${t} to download.`:"This email has no attachments."};return{content:[{type:"text",text:JSON.stringify(r,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Failed to get attachments"))}}async handleSaveAttachment(e){await this.ensureRequiredConnections(!0,!1);const t=e.uid,n=e.savePath,r=e.attachmentIndex,i=e.returnBase64||!1;if("number"!=typeof t)throw new Error("uid must be a number");if("string"!=typeof n||!n)throw new Error("savePath must be a non-empty string");if(!c.isAbsolute(n))throw new Error("savePath must be an absolute path");try{await s(n,{recursive:!0});const e=await this.findMessageInMultipleMailboxes(t);if(!e)throw new Error(`Message with UID ${t} not found in any mailbox`);const l=await this.imapClient.fetchMessageAttachments(t);if(0===l.length)return{content:[{type:"text",text:JSON.stringify({uid:t,note:"This email has no attachments."},null,2)}]};let d;if(void 0!==r){if("number"!=typeof r||r<0||r>=l.length)throw new Error(`Invalid attachmentIndex: ${r}. Valid range: 0-${l.length-1}`);d=[l[r]]}else d=l;const h=[];for(const e of d){const t=c.basename(e.filename);let r=c.join(n,t),s=1;const l=c.extname(t),d=c.basename(t,l);for(;;)try{await o(r),r=c.join(n,`${d}_${s}${l}`),s++}catch{break}await a(r,e.content),console.error(`[Attachment] Saved: ${r} (${e.size} bytes)`);const m={index:e.index,filename:e.filename,contentType:e.contentType,size:e.size,savedPath:r};i&&(m.base64=e.content.toString("base64")),h.push(m)}const m={uid:t,subject:e.subject,totalAttachments:l.length,savedCount:h.length,savedFiles:h,note:`Successfully saved ${h.length} attachment(s) to ${n}`};return{content:[{type:"text",text:JSON.stringify(m,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Failed to save attachment"))}}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:`${f.IMAP.host}:${f.IMAP.port}`,username:f.IMAP.username,tls:f.IMAP.tls,status:"Not connected"},smtp:{connected:!1,serverInfo:`${f.SMTP.host}:${f.SMTP.port}`,username:f.SMTP.username,secure:f.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");if(e.attachments&&e.attachments.length>0){const n=[];for(const t of e.attachments){if(!c.isAbsolute(t))throw new Error(`Attachment path must be absolute: ${t}`);try{const e=await i(t);n.push({filename:c.basename(t),content:e})}catch(e){throw new Error(`Failed to read attachment file: ${t} - ${e instanceof Error?e.message:String(e)}`)}}t.attachments=n}try{const e=await this.smtpClient.sendMail(t);await this.saveSentMessage(t,e.messageId);const n={...e,sentFolderSaved:!0,note:"Email sent successfully and saved to sent folder"};return{content:[{type:"text",text:JSON.stringify(n,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Failed to send email"))}}async handleReplyToEmail(e){await this.ensureRequiredConnections(!0,!0);const t=e.originalUid,n=e.text,r=e.html,s=e.replyToAll||!1,o=!1!==e.includeOriginal;if("number"!=typeof t)throw new Error("originalUid must be a number");try{await this.ensureMailboxOpen(),console.error(`[Reply] Fetching original message with UID: ${t}`);const e=await this.findMessageInMultipleMailboxes(t);if(!e)throw new Error(`Original message with UID ${t} not found in any mailbox`);const a=this.extractEmailFromAddress(e.from);if(!a)throw new Error("Could not extract sender email from original message");let i=[a],c=[];if(s){const t=this.extractEmailsFromAddressField(e.to),n=this.extractEmailsFromAddressField(e.cc),r=[...t,...n].filter(e=>e!==f.IMAP.username&&e!==a);r.length>0&&(c=r)}const l=`Re: ${e.subject||""}`;let d=n,h=r;if(o&&e){const t=e.date?new Date(e.date).toLocaleString():"Unknown Date",s=e.from||"Unknown Sender";if(d=`${n}\n\n${this.buildQuotedText(e.text||"",t,s)}`,r||e.html){const o=this.buildQuotedHtml(e.html||e.text||"",t,s);h=`${r||this.textToHtml(n)}<br><br>${o}`}}const m={to:i,cc:c.length>0?c:void 0,subject:l,text:d,html:h};console.error(`[Reply] Sending reply to: ${i.join(", ")}${c.length>0?` (CC: ${c.join(", ")})`:""}`);const u=await this.smtpClient.sendMail(m);await this.saveSentMessage(m,u.messageId);const p={originalUid:t,originalFrom:a,originalSubject:e.subject,replyToAll:s,includeOriginal:o,recipients:{to:i,cc:c.length>0?c:void 0}},g={...u,replyInfo:p,sentFolderSaved:!0,note:"Reply sent successfully and saved to sent folder"};return{content:[{type:"text",text:JSON.stringify(g,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Failed to reply to email"))}}extractEmailFromAddress(e){if(!e)return null;if(Array.isArray(e)&&e.length>0)return e[0].address||e[0];if("string"==typeof e){const t=e.match(/<([^>]+)>/)||e.match(/([^\s<>]+@[^\s<>]+)/);return t?t[1]:e}return e.address||null}extractEmailsFromAddressField(e){if(!e)return[];if(Array.isArray(e))return e.map(e=>e.address||e).filter(Boolean);if("string"==typeof e)return e.split(",").map(e=>{const t=e.trim().match(/<([^>]+)>/)||e.trim().match(/([^\s<>]+@[^\s<>]+)/);return t?t[1]:e.trim()}).filter(Boolean);const t=this.extractEmailFromAddress(e);return t?[t]:[]}cleanReplySubject(e){return e?e.replace(/^(re:|RE:|回复:|答复:)\s*/i,"").trim():""}detectUnrepliedMessagesAdvanced(e,t,n,r){const s=[],o=[...e].sort((e,t)=>new Date(e.date).getTime()-new Date(t.date).getTime()),a=[...t].sort((e,t)=>new Date(e.date).getTime()-new Date(t.date).getTime());console.error(`[IMAP] Analyzing ${o.length} messages from sender and ${a.length} messages to sender`);const i=Math.min(o.length,100),c=o.slice(0,i);i<o.length&&console.error(`[IMAP] Performance optimization: Processing latest ${i} messages out of ${o.length} total`);for(let e=0;e<c.length;e++){const t=c[e];if(this.isMessageReplied(t,a,n))console.error(`[IMAP] Message UID ${t.uid} (${t.subject}) has been replied`);else if(s.push(t),console.error(`[IMAP] Message UID ${t.uid} (${t.subject}) marked as unreplied`),r&&s.length>=r){console.error(`[IMAP] Early termination: Found ${s.length} unreplied messages (limit: ${r}), stopping further analysis for performance`);break}}return s}isMessageReplied(e,t,n){const r=new Date(e.date),s=this.cleanReplySubject(e.subject||"").toLowerCase();if(t.filter(e=>{const t=new Date(e.date),n=this.cleanReplySubject(e.subject||"").toLowerCase();return t>r&&n===s&&n.length>0}).length>0)return console.error(`[IMAP] Found exact subject match reply for "${s}"`),!0;if(s.length>3&&t.filter(e=>{const t=new Date(e.date),n=this.cleanReplySubject(e.subject||"").toLowerCase();return t>r&&n.includes(s)&&n!==s}).length>0)return console.error(`[IMAP] Found thread-based reply for "${s}"`),!0;const o=new Date(r.getTime()+6048e5);return t.filter(t=>{const n=new Date(t.date);return n>r&&n<=o&&this.isLikelyReplyByContent(e,t)}).length>0&&(console.error(`[IMAP] Found time-window based reply for "${s}"`),!0)}isLikelyReplyByContent(e,t){const n=this.cleanReplySubject(e.subject||"").toLowerCase(),r=this.cleanReplySubject(t.subject||"").toLowerCase();if(n===r&&n.length>0)return!0;if(n.length>5&&r.includes(n))return!0;const s=n.split(/\s+/).filter(e=>e.length>3),o=r.split(/\s+/).filter(e=>e.length>3);return s.length>0&&o.length>0&&s.filter(e=>o.includes(e)).length>=Math.min(2,Math.ceil(s.length/2))}buildQuotedText(e,t,n){return`On ${t}, ${n} wrote:\n${e.split("\n").map(e=>`> ${e}`).join("\n")}`}buildQuotedHtml(e,t,n){return`<div style="border-left: 3px solid #ccc; padding-left: 10px; margin-left: 10px; color: #666;">\n <p><strong>On ${t}, ${n} wrote:</strong></p>\n <div>${e.replace(/\n/g,"<br>")}</div>\n </div>`}textToHtml(e){return e.replace(/\n/g,"<br>").replace(/ /g,"&nbsp;&nbsp;")}async findMessageInMultipleMailboxes(e){if(this.imapClient.getCurrentBox())try{return await this.imapClient.getMessage(e)}catch(e){console.error(`[Search] Message not found in current mailbox: ${e instanceof Error?e.message:String(e)}`)}for(const t of w)try{await this.imapClient.openBox(t,!0);const n=await this.imapClient.getMessage(e);return console.error(`[Search] Found message in mailbox: ${t}`),n}catch(e){console.error(`[Search] Message not found in ${t}: ${e instanceof Error?e.message:String(e)}`)}return null}async ensureMailboxOpen(e="INBOX"){this.imapClient.getCurrentBox()||(console.error(`[IMAP] No mailbox currently open, opening ${e}`),await this.imapClient.openBox(e,!0))}async handleConnectAll(){const e=[];try{if(this.imapClient&&this.imapClient.isConnected()){const t=this.imapClient.getCurrentUsername(),n=f.IMAP.username;t!==n?(console.error(`[IMAP] User mismatch: current=${t}, config=${n}, reconnecting...`),await this.imapClient.disconnect(),await this.ensureIMAPConnection(),e.push("✅ IMAP: Reconnected with correct user")):e.push("ℹ️ IMAP: Already connected")}else await this.ensureIMAPConnection(),e.push("✅ IMAP: Connected successfully")}catch(t){e.push(`❌ IMAP: Connection failed - ${t instanceof Error?t.message:String(t)}`)}try{if(this.smtpClient&&this.smtpClient.isConnected()){const t=this.smtpClient.getCurrentUsername(),n=f.SMTP.username;t!==n?(console.error(`[SMTP] User mismatch: current=${t}, config=${n}, reconnecting...`),await this.smtpClient.disconnect(),await this.ensureSMTPConnection(),e.push("✅ SMTP: Reconnected with correct user")):e.push("ℹ️ SMTP: Already connected")}else 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: ${f.IMAP.host}:${f.IMAP.port} (TLS: ${f.IMAP.tls})`),console.error(`SMTP: ${f.SMTP.host}:${f.SMTP.port} (Secure: ${f.SMTP.secure})`),console.error(`User: ${f.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}}buildRawEmailMessage(e,t){const n=(new Date).toUTCString();let r="";if(r+=`Message-ID: ${t||`<${Date.now()}.${Math.random().toString(36)}@${f.IMAP.host}>`}\r\n`,r+=`Date: ${n}\r\n`,r+=`From: ${f.IMAP.username}\r\n`,r+=`To: ${Array.isArray(e.to)?e.to.join(", "):e.to}\r\n`,e.cc&&(r+=`Cc: ${Array.isArray(e.cc)?e.cc.join(", "):e.cc}\r\n`),e.bcc&&(r+=`Bcc: ${Array.isArray(e.bcc)?e.bcc.join(", "):e.bcc}\r\n`),r+=`Subject: ${e.subject}\r\n`,r+="MIME-Version: 1.0\r\n",e.html&&e.text){const t=`----=_Part_${Date.now()}_${Math.random().toString(36)}`;r+=`Content-Type: multipart/alternative; boundary="${t}"\r\n\r\n`,r+=`--${t}\r\n`,r+="Content-Type: text/plain; charset=utf-8\r\n",r+="Content-Transfer-Encoding: 8bit\r\n\r\n",r+=`${e.text}\r\n\r\n`,r+=`--${t}\r\n`,r+="Content-Type: text/html; charset=utf-8\r\n",r+="Content-Transfer-Encoding: 8bit\r\n\r\n",r+=`${e.html}\r\n\r\n`,r+=`--${t}--\r\n`}else e.html?(r+="Content-Type: text/html; charset=utf-8\r\n",r+="Content-Transfer-Encoding: 8bit\r\n\r\n",r+=`${e.html}\r\n`):(r+="Content-Type: text/plain; charset=utf-8\r\n",r+="Content-Transfer-Encoding: 8bit\r\n\r\n",r+=`${e.text||""}\r\n`);return r}async saveSentMessage(e,t){try{await this.ensureRequiredConnections(!0,!1);const n=this.buildRawEmailMessage(e,t);await this.imapClient.saveMessageToFolder(n),console.error("[Email] Message saved to sent folder successfully")}catch(e){console.error("[Email] Failed to save message to sent folder:",e instanceof Error?e.message:String(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 n,CallToolRequestSchema as r}from"@modelcontextprotocol/sdk/types.js";import{mkdir as s,access as i,writeFile as o,readFile as a}from"fs/promises";import c from"path";import l from"imap";import{EventEmitter as d}from"events";import{simpleParser as h}from"mailparser";import m from"nodemailer";class IMAPClient extends d{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 n={user:this.config.username,password:this.config.password,host:this.config.host,port:this.config.port,tls:this.config.tls||!1,tlsOptions:{rejectUnauthorized:!1,servername:this.config.host},connTimeout:this.config.connTimeout??6e4,authTimeout:this.config.authTimeout??3e4,socketTimeout:this.config.socketTimeout??6e4,keepalive:!1!==this.config.keepalive};this.imap=new l(n),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((n,r)=>{this.imap.openBox(e,t,(t,s)=>{if(t)return console.error(`[IMAP] Failed to open box ${e}:`,t.message),void r(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};n(i)})})}async getBoxes(){if(!this.imap||!this.authenticated)throw new Error("Not connected or authenticated");return new Promise((e,t)=>{this.imap.getBoxes((n,r)=>{n?t(new Error(`Failed to get boxes: ${n.message}`)):e(r)})})}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,n)=>{this.imap.search(e,(e,r)=>{if(e)return console.error("[IMAP] Search failed:",e.message),void n(new Error(`Search failed: ${e.message}`));console.error(`[IMAP] Search found ${r.length} messages`),t(r)})})}async fetchMessages(e,t={}){if(!this.imap)throw new Error("Not connected to IMAP server");this.currentBox||await this.openBox("INBOX",!0);const n={bodies:t.bodies||["HEADER","TEXT"],struct:!1!==t.struct,envelope:!1!==t.envelope,markSeen:t.markSeen||!1,...t};return new Promise((t,r)=>{const s=[],i=new Map;if(0===e.length)return void t(s);const o=this.imap.fetch(e,n);o.on("message",(e,t)=>{console.error(`[IMAP] Processing message ${t}`);let n={},r="";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");n=this.parseHeaders(t)}else"TEXT"===t.which&&(r=e.toString("utf8"))})}),e.once("attributes",e=>{o.uid=e.uid,o.flags=e.flags||[];const t=e.date||new Date;o.date=(t instanceof Date?t:new Date(t)).toISOString(),o.size=e.size||0}),e.once("end",()=>{console.error(`[IMAP] Message ${t} processed, preparing for parse`),i.set(t,{message:o,headers:n,body:r,rawBuffer:Buffer.concat(s)})})}),o.once("error",e=>{console.error("[IMAP] Fetch error:",e.message),r(new Error(`Fetch failed: ${e.message}`))}),o.once("end",async()=>{console.error(`[IMAP] Fetch completed, parsing ${i.size} messages`);for(const[e,t]of i)try{const e=await h(t.rawBuffer),n=e=>e?Array.isArray(e)?e.map(e=>r(e)).filter(Boolean).join(", "):r(e):"",r=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""},i=(e.attachments||[]).map((e,t)=>({index:t,filename:e.filename||`attachment_${t+1}${e.contentType&&"."+e.contentType.split("/")[1]?.split(";")[0]||""}`,contentType:e.contentType||"application/octet-stream",size:e.size||(e.content?e.content.length:0),contentId:e.contentId||void 0,contentDisposition:e.contentDisposition||void 0}));s.push({...t.message,subject:e.subject||"No Subject",from:n(e.from),to:n(e.to),cc:n(e.cc)||void 0,bcc:n(e.bcc)||void 0,text:e.text,html:e.html,attachments:i.length>0?i:void 0})}catch(n){console.error(`[IMAP] Failed to parse message ${e}:`,n),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 fetchMessageAttachments(e){if(!this.imap)throw new Error("Not connected to IMAP server");return this.currentBox||await this.openBox("INBOX",!0),new Promise((t,n)=>{const r=[],s=this.imap.fetch([e],{bodies:["HEADER","TEXT"],struct:!0,markSeen:!1});s.on("message",e=>{e.on("body",e=>{e.on("data",e=>{r.push(e)})})}),s.once("error",e=>{n(new Error(`Fetch attachments failed: ${e.message}`))}),s.once("end",async()=>{try{const e=Buffer.concat(r),n=((await h(e)).attachments||[]).map((e,t)=>({index:t,filename:e.filename||`attachment_${t+1}${e.contentType&&"."+e.contentType.split("/")[1]?.split(";")[0]||""}`,contentType:e.contentType||"application/octet-stream",size:e.size||(e.content?e.content.length:0),contentId:e.contentId||void 0,contentDisposition:e.contentDisposition||void 0,content:e.content}));t(n)}catch(e){n(new Error(`Failed to parse attachments: ${e instanceof Error?e.message:String(e)}`))}})})}async deleteMessage(e){if(!this.imap)throw new Error("Not connected to IMAP server");return await this.openBox(this.currentBox||"INBOX",!1),new Promise((t,n)=>{this.imap.addFlags(e,["\\Deleted"],r=>{if(r)return console.error(`[IMAP] Failed to mark message ${e} as deleted:`,r.message),void n(new Error(`Failed to delete message: ${r.message}`));console.error(`[IMAP] Message ${e} marked for deletion`),this.imap.expunge(r=>{if(r)return console.error("[IMAP] Failed to expunge:",r.message),void n(new Error(`Failed to expunge deleted messages: ${r.message}`));console.error(`[IMAP] Message ${e} deleted successfully`),t()})})})}async getMessageCount(){return(await this.openBox("INBOX",!0)).messages.total}async getUnseenMessages(e=50){await this.openBox("INBOX",!0);const t=(await this.search(["UNSEEN"])).slice(-e);return this.fetchMessages(t)}async getRecentMessages(e=50){await this.openBox("INBOX",!0);const t=(await this.search(["ALL"])).slice(-e);return this.fetchMessages(t)}parseHeaders(e){const t={},n=e.split("\r\n");let r="",s="";for(const e of n)if(e.match(/^\s/)&&r)s+=" "+e.trim();else{r&&(t[r.toLowerCase()]=s.trim());const n=e.indexOf(":");n>-1?(r=e.substring(0,n).trim(),s=e.substring(n+1).trim()):(r="",s="")}return r&&(t[r.toLowerCase()]=s.trim()),t}async disconnect(){if(this.imap)return this.connected?new Promise(e=>{const t=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(t),console.error("[IMAP] Disconnected"),this.connected=!1,this.authenticated=!1,this.currentBox=null,this.imap=null,e()}),this.imap.once("error",n=>{clearTimeout(t),console.error("[IMAP] Disconnect error:",n.message),this.connected=!1,this.authenticated=!1,this.currentBox=null,this.imap=null,e()});try{this.imap.end()}catch(n){clearTimeout(t),console.error("[IMAP] Error calling end():",n),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}getCurrentUsername(){return this.config?.username||null}async saveMessageToFolder(e,t){if(!this.connected)throw new Error("IMAP client is not connected");return new Promise((n,r)=>{this.imap.openBox(t,!1,s=>{s?r(new Error(`Failed to open folder ${t}: ${s.message}`)):this.saveToOpenedFolder(e,t,n,r)})})}saveToOpenedFolder(e,t,n,r){this.imap.append(e,{mailbox:t},e=>{e?(console.error(`[IMAP] Failed to save message to ${t}:`,e.message),r(new Error(`Failed to save message to ${t}: ${e.message}`))):n()})}}class SMTPClient{transporter=null;config;constructor(e){this.config=e}async connect(){this.transporter=m.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)}`)}}getCurrentUsername(){return this.config?.username||null}isConnected(){return null!==this.transporter}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 u(e,t){const n=process.env[e];if(!n)throw new Error(`Missing required environment variable: ${e}. Please set this variable in your MCP server configuration.`);return n}function p(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 f(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 g={IMAP:{host:u("IMAP_HOST"),port:f("IMAP_PORT"),username:u("EMAIL_USER"),password:u("EMAIL_PASS"),tls:p("IMAP_SECURE")},SMTP:{host:u("SMTP_HOST"),port:f("SMTP_PORT"),username:u("EMAIL_USER"),password:u("EMAIL_PASS"),secure:p("SMTP_SECURE")}},y=["INBOX.Sent","Sent","SENT","Sent Items","Sent Messages","已发送"],w=["INBOX",...y];(new class{server;imapClient=null;smtpClient=null;isInitializing=!1;isSmtpInitializing=!1;sentMailboxName=void 0;formatError(e,t){return`${t}: ${e instanceof Error?e.message:String(e)}`}isDateOnly(e){return[/^\d{4}-\d{2}-\d{2}$/,/^\d{2}-\w{3}-\d{4}$/,/^\w{3}\s+\d{1,2},?\s+\d{4}$/].some(t=>t.test(e.trim()))}filterMessagesByDateRange(e,t,n){if(!t&&!n)return e;let r=null,s=null;t&&(r=new Date(t),isNaN(r.getTime())?(console.error(`[Filter] Invalid start date format: ${t}, skipping start date filter`),r=null):console.error(`[Filter] Start date parsed as: ${r.toISOString()}`)),n&&(s=new Date(n),isNaN(s.getTime())?(console.error(`[Filter] Invalid end date format: ${n}, skipping end date filter`),s=null):this.isDateOnly(n)?(s.setHours(23,59,59,999),console.error(`[Filter] End date adjusted to end of day: ${s.toISOString()}`)):console.error(`[Filter] End date parsed as: ${s.toISOString()}`));const i=e.length,o=e.filter(e=>{if(!e.date)return!0;const t=new Date(e.date);return!(!isNaN(t.getTime())&&(r&&t<r||s&&t>s))});return console.error(`[Filter] Date filtering: ${i} -> ${o.length} messages`),o}async searchInMultipleMailboxes(e,t,n,r="",s="",i=!1,o){const a=i?null:await this.findSentMailbox(),c=a?["INBOX",a]:["INBOX"],l={searchType:t,searchValue:n,searchCriteria:e,mailboxesSearched:[],totalMatches:0,messages:[]};for(const t of c)try{console.error(`[IMAP] Searching in mailbox: ${t}`),await this.imapClient.openBox(t,!0);const n=await this.imapClient.search(e);console.error(`[IMAP] Found ${n.length} messages in ${t}`);let i=[],a=[];if(n.length>0){const e=o?n.slice(-o):n;console.error(`[IMAP] Auto-fetching content for ${e.length} messages from ${t}${o?` (limited from ${n.length})`:""}`);let c=(await this.imapClient.fetchMessages(e)).map(e=>({...e,sourceMailbox:t}));(r||s)&&(c=this.filterMessagesByDateRange(c,r,s)),i=c,a=c.map(e=>e.uid),l.messages.push(...i)}l.mailboxesSearched.push({mailbox:t,matchingUIDs:a,messageCount:i.length})}catch(e){console.error(`[IMAP] Error searching in ${t}:`,e),l.mailboxesSearched.push({mailbox:t,error:`Failed to search: ${e instanceof Error?e.message:String(e)}`,matchingUIDs:[],messageCount:0})}if(l.totalMatches=l.messages.length,l.messages.sort((e,t)=>{const n=new Date(e.date||0);return new Date(t.date||0).getTime()-n.getTime()}),o&&l.messages.length>o&&(l.messages=l.messages.slice(0,o)),l.totalMatches>0){let e=`Found and retrieved ${l.totalMatches} messages across ${l.mailboxesSearched.length} mailboxes`;(r||s)&&(e+=" (filtered by date range)"),l.note=e}else l.note="No messages found in any of the searched mailboxes";return a||(l.warning="Could not find sent mailbox - only searched INBOX"),l}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(n,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 INBOX. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{}}},{name:"get_unseen_messages",description:"Get unseen (unread) messages. Returns the most recent unseen messages up to the specified limit. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{limit:{type:"number",description:"Maximum number of messages to fetch (default: 50)",default:50,minimum:1}}}},{name:"get_recent_messages",description:"Get recent messages. Returns the most recent messages up to the specified limit. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{limit:{type:"number",description:"Maximum number of messages to fetch (default: 50)",default:50,minimum:1}}}},{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/time for filtering. Supports multiple formats: "2025-07-01", "2025-07-01 14:30:00", "01-Jul-2025", or ISO format. Leave empty to not filter by start date.'},endDate:{type:"string",description:'Optional end date/time for filtering. Supports multiple formats: "2025-08-01", "2025-08-01 23:59:59", "01-Aug-2025", or ISO format. Leave empty to not filter by end date.'},inboxOnly:{type:"boolean",description:"If true, only search INBOX and skip the sent folder (default: false).",default:!1}},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/time for filtering. Supports multiple formats: "2025-07-01", "2025-07-01 14:30:00", "01-Jul-2025", or ISO format. Leave empty to not filter by start date.'},endDate:{type:"string",description:'Optional end date/time for filtering. Supports multiple formats: "2025-08-01", "2025-08-01 23:59:59", "01-Aug-2025", or ISO format. Leave empty to not filter by end date.'},inboxOnly:{type:"boolean",description:"If true, only search INBOX and skip the sent folder (default: false).",default:!1}},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/time for filtering. Supports multiple formats: "2025-07-01", "2025-07-01 14:30:00", "01-Jul-2025", or ISO format. Leave empty to not filter by start date.'},endDate:{type:"string",description:'Optional end date/time for filtering. Supports multiple formats: "2025-08-01", "2025-08-01 23:59:59", "01-Aug-2025", or ISO format. Leave empty to not filter by end date.'},inboxOnly:{type:"boolean",description:"If true, only search INBOX and skip the sent folder (default: false).",default:!1}},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"'},inboxOnly:{type:"boolean",description:"If true, only search INBOX and skip the sent folder (default: false).",default:!1}},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/time for filtering. Supports multiple formats: "2025-07-01", "2025-07-01 14:30:00", "01-Jul-2025", or ISO format. Leave empty to not filter by start date.'},endDate:{type:"string",description:'Optional end date/time for filtering. Supports multiple formats: "2025-08-01", "2025-08-01 23:59:59", "01-Aug-2025", or ISO format. Leave empty to not filter by end date.'},inboxOnly:{type:"boolean",description:"If true, only search INBOX and skip the sent folder (default: false).",default:!1}},required:["sender"]}},{name:"search_unreplied_from_sender",description:"Search unreplied messages from a specific sender with optional date range. Identifies messages that have not been replied to by checking for corresponding reply messages. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{sender:{type:"string",description:"Email address of the sender to search for unreplied messages"},startDate:{type:"string",description:'Optional start date/time for filtering. Supports multiple formats: "2025-07-01", "2025-07-01 14:30:00", "01-Jul-2025", or ISO format. Leave empty to not filter by start date.'},endDate:{type:"string",description:'Optional end date/time for filtering. Supports multiple formats: "2025-08-01", "2025-08-01 23:59:59", "01-Aug-2025", or ISO format. Leave empty to not filter by end date.'},limit:{type:"number",description:"Maximum number of messages to process from each search (default: 10, maximum: 200). Since unreplied emails are typically few, smaller limits are recommended.",default:10},inboxOnly:{type:"boolean",description:"If true, only search INBOX and skip the sent folder (default: false).",default:!1}},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/time for filtering. Supports multiple formats: "2025-07-01", "2025-07-01 14:30:00", "01-Jul-2025", or ISO format. Leave empty to not filter by start date.'},endDate:{type:"string",description:'Optional end date/time for filtering. Supports multiple formats: "2025-08-01", "2025-08-01 23:59:59", "01-Aug-2025", or ISO format. Leave empty to not filter by end date.'},inboxOnly:{type:"boolean",description:"If true, only search INBOX and skip the sent folder (default: false).",default:!1}},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/time for filtering. Supports multiple formats: "2025-07-01", "2025-07-01 14:30:00", "01-Jul-2025", or ISO format. Leave empty to not filter by start date.'},endDate:{type:"string",description:'Optional end date/time for filtering. Supports multiple formats: "2025-08-01", "2025-08-01 23:59:59", "01-Aug-2025", or ISO format. Leave empty to not filter by end date.'},inboxOnly:{type:"boolean",description:"If true, only search INBOX and skip the sent folder (default: false).",default:!1}},required:["keyword"]}},{name:"search_all_messages",description:"Search all messages across mailboxes with optional date range and limit. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{startDate:{type:"string",description:'Optional start date/time for filtering. Supports multiple formats: "2025-07-01", "2025-07-01 14:30:00", "01-Jul-2025", or ISO format. Leave empty to not filter by start date.'},endDate:{type:"string",description:'Optional end date/time for filtering. Supports multiple formats: "2025-08-01", "2025-08-01 23:59:59", "01-Aug-2025", or ISO format. Leave empty to not filter by end date.'},limit:{type:"number",description:"Maximum number of messages to return (default: 50). Use a smaller value for faster results.",default:50},inboxOnly:{type:"boolean",description:"If true, only search INBOX and skip the sent folder (default: false).",default:!1}},required:[]}},{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)"},attachments:{type:"array",description:"Array of absolute file paths to attach (optional)",items:{type:"string"}}},required:["to","subject"]}},{name:"reply_to_email",description:"Reply to a specific email by UID. Automatically sets reply headers, adds Re: prefix, and includes original message. Auto-connects to both IMAP and SMTP if not already connected.",inputSchema:{type:"object",properties:{originalUid:{type:"number",description:"UID of the original message to reply to"},text:{type:"string",description:"Reply message text"},html:{type:"string",description:"Reply message HTML (optional)"},replyToAll:{type:"boolean",description:"Reply to all recipients instead of just sender (default: false)",default:!1},includeOriginal:{type:"boolean",description:"Include original message in reply (default: true)",default:!0}},required:["originalUid"]}},{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_attachments",description:"Get attachment metadata (filename, size, contentType, index) for a specific email by UID. Does not download attachment content. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{uid:{type:"number",description:"Message UID to get attachments for"}},required:["uid"]}},{name:"save_attachment",description:"Download and save email attachments to local file system. Can save a single attachment by index or all attachments. Auto-connects if not already connected.",inputSchema:{type:"object",properties:{uid:{type:"number",description:"Message UID containing the attachment(s)"},savePath:{type:"string",description:"Absolute path to the directory where attachments will be saved"},attachmentIndex:{type:"number",description:"Index of the specific attachment to save (0-based). If omitted, all attachments will be saved."},returnBase64:{type:"boolean",description:"Whether to also return base64-encoded content in the response (default: false)",default:!1}},required:["uid","savePath"]}},{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(r,async e=>{const{name:t,arguments:n}=e.params;try{switch(t){case"open_mailbox":return await this.handleOpenMailbox(n||{});case"list_mailboxes":return await this.handleListMailboxes();case"search_by_sender":return await this.handleSearchBySender(n||{});case"search_by_subject":return await this.handleSearchBySubject(n);case"search_by_recipient":return await this.handleSearchByRecipient(n);case"search_since_date":return await this.handleSearchSinceDate(n);case"search_unread_from_sender":return await this.handleSearchUnreadFromSender(n);case"search_unreplied_from_sender":return await this.handleSearchUnrepliedFromSender(n);case"search_by_body":return await this.handleSearchByBody(n);case"search_with_keyword":return await this.handleSearchWithKeyword(n);case"search_all_messages":return await this.handleSearchAllMessages(n);case"get_messages":return await this.handleGetMessages(n);case"get_message":return await this.handleGetMessage(n);case"delete_message":return await this.handleDeleteMessage(n);case"get_attachments":return await this.handleGetAttachments(n);case"save_attachment":return await this.handleSaveAttachment(n);case"get_message_count":return await this.handleGetMessageCount();case"get_unseen_messages":return await this.handleGetUnseenMessages(n?.limit);case"get_recent_messages":return await this.handleGetRecentMessages(n?.limit);case"get_connection_status":return await this.handleGetConnectionStatus();case"send_email":return await this.handleSendEmail(n);case"reply_to_email":return await this.handleReplyToEmail(n);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){const e=Date.now()+3e4;for(;this.isInitializing;){if(Date.now()>e)throw new Error("IMAP connection initialization timed out");await new Promise(e=>setTimeout(e,100))}if(!this.imapClient||!this.imapClient.isConnected())throw new Error("IMAP connection initialization failed");return}this.isInitializing=!0;try{const e=g.IMAP;console.error(`[IMAP] Auto-connecting to ${e.host}:${e.port}`),this.imapClient=new IMAPClient(e),this.sentMailboxName=void 0,await this.imapClient.connect(),console.error("[IMAP] Auto-connection successful")}finally{this.isInitializing=!1}}}async ensureSMTPConnection(){if(!this.smtpClient){if(this.isSmtpInitializing){const e=Date.now()+3e4;for(;this.isSmtpInitializing;){if(Date.now()>e)throw new Error("SMTP connection initialization timed out");await new Promise(e=>setTimeout(e,100))}if(!this.smtpClient)throw new Error("SMTP connection initialization failed");return}this.isSmtpInitializing=!0;try{const e=g.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")}finally{this.isSmtpInitializing=!1}}}async ensureRequiredConnections(e=!1,t=!1){e&&await this.ensureIMAPConnection(),t&&await this.ensureSMTPConnection()}async findSentMailbox(){if(void 0!==this.sentMailboxName)return this.sentMailboxName;try{const e=await this.imapClient.getBoxes(),t=(e,n="")=>{for(const[r,s]of Object.entries(e)){const e=n?`${n}${s.delimiter||"."}${r}`:r;if(Array.isArray(s.attribs)&&s.attribs.includes("\\Sent"))return e;if(s.children){const n=t(s.children,e);if(n)return n}}return null},n=t(e);if(n)return console.error(`[IMAP] Sent mailbox found via \\Sent attribute: ${n}`),this.sentMailboxName=n,n}catch(e){console.error("[IMAP] getBoxes failed during sent mailbox detection:",e instanceof Error?e.message:String(e))}for(const e of y)try{return await this.imapClient.openBox(e,!0),console.error(`[IMAP] Sent mailbox found by name fallback: ${e}`),this.sentMailboxName=e,e}catch{}return console.error("[IMAP] No sent mailbox found"),this.sentMailboxName=null,null}async handleOpenMailbox(e){await this.ensureRequiredConnections(!0,!1);const t=e.mailboxName||"INBOX",n=e.readOnly||!1,r=!1!==e.openSent;try{const e={},s=await this.imapClient.openBox(t,n);if(e[t]=s,e.currentlyOpen=t,r){const r=await this.findSentMailbox();if(r&&r!==t)try{const s=await this.imapClient.openBox(r,!0);e[r]=s,await this.imapClient.openBox(t,n),e.currentlyOpen=t,e.note=`Retrieved info from both ${t} and ${r}. Currently open: ${t}`}catch(t){e.sentBoxError=`Failed to access sent mailbox ${r}: ${t instanceof Error?t.message:String(t)}`}else r||(e.sentBoxWarning="Could not find any sent mailbox")}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,n="",r=new Set)=>{if(r.has(e))return{name:n,circular:!0};r.add(e);const s={name:n,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=n?`${n}${e.delimiter||"."}${i}`:i;s.children[i]=t(o,a,new Set(r))}}return r.delete(e),s},n={};for(const[r,s]of Object.entries(e))n[r]=t(s,r);return{content:[{type:"text",text:JSON.stringify(n,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,n=e.startDate||"",r=e.endDate||"";if(!t)throw new Error("sender parameter is required");try{const s=[["FROM",t]];console.error("[IMAP] Searching messages from sender across all mailboxes:",t);const i=await this.searchInMultipleMailboxes(s,"By Sender",t,n,r,e.inboxOnly||!1);return i.sender=t,n&&(i.startDate=n),r&&(i.endDate=r),{content:[{type:"text",text:JSON.stringify(i,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,n=e.startDate||"",r=e.endDate||"";if(!t)throw new Error("subject parameter is required");try{const s=[["SUBJECT",t]];console.error("[IMAP] Searching messages with subject across all mailboxes:",t);const i=await this.searchInMultipleMailboxes(s,"By Subject",t,n,r,e.inboxOnly||!1);return i.subjectKeywords=t,n&&(i.startDate=n),r&&(i.endDate=r),{content:[{type:"text",text:JSON.stringify(i,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,n=e.startDate||"",r=e.endDate||"";if(!t)throw new Error("recipient parameter is required");try{const s=[["TO",t]];console.error("[IMAP] Searching messages to recipient across all mailboxes:",t);const i=await this.searchInMultipleMailboxes(s,"By Recipient",t,n,r,e.inboxOnly||!1);return i.recipient=t,n&&(i.startDate=n),r&&(i.endDate=r),{content:[{type:"text",text:JSON.stringify(i,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 n=new Date(t);if(isNaN(n.getTime()))throw new Error(`Invalid date format: ${t}. Use formats like "2026-03-17", "17-Mar-2026", or "March 17, 2026"`);const r=[["SINCE",n]];console.error("[IMAP] Searching messages since date across all mailboxes:",t);const s=await this.searchInMultipleMailboxes(r,"Since Date",t,"","",e.inboxOnly||!1);return s.sinceDate=t,s.note='Date format should be like "April 20, 2010" or "20-Apr-2010". Searched across multiple mailboxes.',{content:[{type:"text",text:JSON.stringify(s,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,n=e.startDate||"",r=e.endDate||"";if(!t)throw new Error("sender parameter is required");try{const s=["UNSEEN",["FROM",t]];console.error("[IMAP] Searching unread messages from sender across all mailboxes:",t);const i=await this.searchInMultipleMailboxes(s,"Unread messages from specific sender",t,n,r,e.inboxOnly||!1);return i.sender=t,n&&(i.startDate=n),r&&(i.endDate=r),i.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(i,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Search unread from sender failed"))}}async handleSearchUnrepliedFromSender(e){await this.ensureRequiredConnections(!0,!1);const t=e.sender,n=e.startDate||"",r=e.endDate||"",s=Math.min(Math.max(e.limit||10,1),200);if(!t)throw new Error("sender parameter is required");try{console.error(`[IMAP] Searching unreplied messages from sender: ${t}`);const i=await this.searchInMultipleMailboxes([["FROM",t]],"From Sender",t,n,r,e.inboxOnly||!1,s),o=await this.searchInMultipleMailboxes([["TO",t]],"To Sender",t,n,r,e.inboxOnly||!1,s);console.error(`[IMAP] Found ${i.messages.length} messages from sender, ${o.messages.length} messages to sender`),i.messages.length>s&&(console.error(`[IMAP] Warning: Found ${i.messages.length} messages from sender, processing only the latest ${s}`),i.messages=i.messages.sort((e,t)=>new Date(t.date).getTime()-new Date(e.date).getTime()).slice(0,s));const a=this.detectUnrepliedMessagesAdvanced(i.messages,o.messages,t,s);console.error(`[IMAP] Found ${a.length} unreplied messages using advanced detection`);const c={searchType:"Unreplied messages from sender (Advanced)",searchValue:t,searchCriteria:["FROM",t],mailboxesSearched:i.mailboxesSearched,totalMatches:a.length,messages:a,sender:t,note:`Found ${a.length} unreplied messages from ${t} using advanced thread-aware detection.${s<200?` (limited to ${s} messages per search)`:""}`};return n&&(c.startDate=n),r&&(c.endDate=r),(n||r)&&(c.note+=" (filtered by date range)"),{content:[{type:"text",text:JSON.stringify(c,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Search unreplied from sender failed"))}}async handleSearchByBody(e){await this.ensureRequiredConnections(!0,!1);const t=e.text,n=e.startDate||"",r=e.endDate||"";if(!t)throw new Error("text parameter is required");try{const s=[["BODY",t]];console.error("[IMAP] Searching messages with body text across all mailboxes:",t);const i=await this.searchInMultipleMailboxes(s,"By Body Text",t,n,r,e.inboxOnly||!1);return i.bodyText=t,n&&(i.startDate=n),r&&(i.endDate=r),{content:[{type:"text",text:JSON.stringify(i,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,n=e.startDate||"",r=e.endDate||"";if(!t)throw new Error("keyword parameter is required");try{const s=[["KEYWORD",t]];console.error("[IMAP] Searching messages with keyword across all mailboxes:",t);const i=await this.searchInMultipleMailboxes(s,"With Keyword",t,n,r,e.inboxOnly||!1);return i.keyword=t,n&&(i.startDate=n),r&&(i.endDate=r),{content:[{type:"text",text:JSON.stringify(i,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Search with keyword failed"))}}async handleSearchAllMessages(e){await this.ensureRequiredConnections(!0,!1);const t=e.startDate||"",n=e.endDate||"",r=e.limit||50;try{const s=["ALL"];console.error(`[IMAP] Searching all messages across mailboxes, limit: ${r}`);const i=await this.searchInMultipleMailboxes(s,"All Messages","*",t,n,e.inboxOnly||!1,r);return t&&(i.startDate=t),n&&(i.endDate=n),{content:[{type:"text",text:JSON.stringify(i,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Search all messages 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 n=e.markSeen||!1;try{let e=[];const r=new Set,s=[...t];for(const t of w){if(0===s.length)break;try{await this.imapClient.openBox(t,!0);const i=await this.imapClient.fetchMessages(s,{markSeen:n});i.length>0&&(e.push(...i),i.forEach(e=>{r.add(e.uid);const t=s.indexOf(e.uid);t>-1&&s.splice(t,1)}),console.error(`[GetMessages] Found ${i.length} messages in mailbox: ${t}`))}catch(e){console.error(`[GetMessages] Failed to search in ${t}: ${e instanceof Error?e.message:String(e)}`)}}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");const n=e.markSeen||!1;try{const e=await this.findMessageInMultipleMailboxes(t);if(!e)throw new Error(`Message with UID ${t} not found in any mailbox`);if(n)try{const e=await this.imapClient.fetchMessages([t],{markSeen:!0});if(e.length>0)return{content:[{type:"text",text:JSON.stringify(e[0],null,2)}]}}catch(e){console.error(`[GetMessage] Failed to mark message as seen: ${e instanceof Error?e.message:String(e)}`)}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{if(!await this.findMessageInMultipleMailboxes(t))throw new Error(`Message with UID ${t} not found in any mailbox`);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 handleGetAttachments(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.findMessageInMultipleMailboxes(t);if(!e)throw new Error(`Message with UID ${t} not found in any mailbox`);const n=e.attachments||[],r={uid:t,subject:e.subject,from:e.from,attachmentCount:n.length,attachments:n.map(e=>({index:e.index,filename:e.filename,contentType:e.contentType,size:e.size,contentId:e.contentId,contentDisposition:e.contentDisposition})),note:n.length>0?`Found ${n.length} attachment(s). Use save_attachment tool with uid=${t} to download.`:"This email has no attachments."};return{content:[{type:"text",text:JSON.stringify(r,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Failed to get attachments"))}}async handleSaveAttachment(e){await this.ensureRequiredConnections(!0,!1);const t=e.uid,n=e.savePath,r=e.attachmentIndex,a=e.returnBase64||!1;if("number"!=typeof t)throw new Error("uid must be a number");if("string"!=typeof n||!n)throw new Error("savePath must be a non-empty string");if(!c.isAbsolute(n))throw new Error("savePath must be an absolute path");try{await s(n,{recursive:!0});const e=await this.findMessageInMultipleMailboxes(t);if(!e)throw new Error(`Message with UID ${t} not found in any mailbox`);const l=await this.imapClient.fetchMessageAttachments(t);if(0===l.length)return{content:[{type:"text",text:JSON.stringify({uid:t,note:"This email has no attachments."},null,2)}]};let d;if(void 0!==r){if("number"!=typeof r||r<0||r>=l.length)throw new Error(`Invalid attachmentIndex: ${r}. Valid range: 0-${l.length-1}`);d=[l[r]]}else d=l;const h=[];for(const e of d){const t=c.basename(e.filename);let r=c.join(n,t),s=1;const l=c.extname(t),d=c.basename(t,l);for(;;)try{await i(r),r=c.join(n,`${d}_${s}${l}`),s++}catch{break}await o(r,e.content),console.error(`[Attachment] Saved: ${r} (${e.size} bytes)`);const m={index:e.index,filename:e.filename,contentType:e.contentType,size:e.size,savedPath:r};a&&(m.base64=e.content.toString("base64")),h.push(m)}const m={uid:t,subject:e.subject,totalAttachments:l.length,savedCount:h.length,savedFiles:h,note:`Successfully saved ${h.length} attachment(s) to ${n}`};return{content:[{type:"text",text:JSON.stringify(m,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Failed to save attachment"))}}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(e){await this.ensureRequiredConnections(!0,!1);try{const t=e&&e>0?e:50,n=await this.imapClient.getUnseenMessages(t);return{content:[{type:"text",text:JSON.stringify(n,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Failed to get unseen messages"))}}async handleGetRecentMessages(e){await this.ensureRequiredConnections(!0,!1);try{const t=e&&e>0?e:50,n=await this.imapClient.getRecentMessages(t);return{content:[{type:"text",text:JSON.stringify(n,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:`${g.IMAP.host}:${g.IMAP.port}`,username:g.IMAP.username,tls:g.IMAP.tls,status:"Not connected"},smtp:{connected:!1,serverInfo:`${g.SMTP.host}:${g.SMTP.port}`,username:g.SMTP.username,secure:g.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");if(e.attachments&&e.attachments.length>0){const n=[];for(const t of e.attachments){if(!c.isAbsolute(t))throw new Error(`Attachment path must be absolute: ${t}`);try{const e=await a(t);n.push({filename:c.basename(t),content:e})}catch(e){throw new Error(`Failed to read attachment file: ${t} - ${e instanceof Error?e.message:String(e)}`)}}t.attachments=n}try{const e=await this.smtpClient.sendMail(t),n=await this.saveSentMessage(t,e.messageId),r={...e,sentFolderSaved:n,note:n?"Email sent successfully and saved to sent folder":"Email sent successfully (note: could not save to sent folder)"};return{content:[{type:"text",text:JSON.stringify(r,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Failed to send email"))}}async handleReplyToEmail(e){await this.ensureRequiredConnections(!0,!0);const t=e.originalUid,n=e.text,r=e.html,s=e.replyToAll||!1,i=!1!==e.includeOriginal;if("number"!=typeof t)throw new Error("originalUid must be a number");if(!e.text&&!e.html)throw new Error("Either text or html content is required");try{await this.ensureMailboxOpen(),console.error(`[Reply] Fetching original message with UID: ${t}`);const e=await this.findMessageInMultipleMailboxes(t);if(!e)throw new Error(`Original message with UID ${t} not found in any mailbox`);const o=this.extractEmailFromAddress(e.from);if(!o)throw new Error("Could not extract sender email from original message");let a=[o],c=[];if(s){const t=this.extractEmailsFromAddressField(e.to),n=this.extractEmailsFromAddressField(e.cc),r=[...t,...n].filter(e=>e!==g.IMAP.username&&e!==o);r.length>0&&(c=r)}const l=`Re: ${e.subject||""}`;let d=n,h=r;if(i&&e){const t=e.date?new Date(e.date).toLocaleString():"Unknown Date",s=e.from||"Unknown Sender";if(d=`${n||""}\n\n${this.buildQuotedText(e.text||"",t,s)}`,r||e.html){const i=this.buildQuotedHtml(e.html||e.text||"",t,s);h=`${r||this.textToHtml(n||"")}<br><br>${i}`}}const m={to:a,cc:c.length>0?c:void 0,subject:l,text:d,html:h};console.error(`[Reply] Sending reply to: ${a.join(", ")}${c.length>0?` (CC: ${c.join(", ")})`:""}`);const u=await this.smtpClient.sendMail(m),p=await this.saveSentMessage(m,u.messageId),f={originalUid:t,originalFrom:o,originalSubject:e.subject,replyToAll:s,includeOriginal:i,recipients:{to:a,cc:c.length>0?c:void 0}},y={...u,replyInfo:f,sentFolderSaved:p,note:p?"Reply sent successfully and saved to sent folder":"Reply sent successfully (note: could not save to sent folder)"};return{content:[{type:"text",text:JSON.stringify(y,null,2)}]}}catch(e){throw new Error(this.formatError(e,"Failed to reply to email"))}}extractEmailFromAddress(e){if(!e)return null;if(Array.isArray(e)&&e.length>0)return e[0].address||e[0];if("string"==typeof e){const t=e.match(/<([^>]+)>/)||e.match(/([^\s<>]+@[^\s<>]+)/);return t?t[1]:e}return e.address||null}extractEmailsFromAddressField(e){if(!e)return[];if(Array.isArray(e))return e.map(e=>e.address||e).filter(Boolean);if("string"==typeof e)return e.split(",").map(e=>{const t=e.trim().match(/<([^>]+)>/)||e.trim().match(/([^\s<>]+@[^\s<>]+)/);return t?t[1]:e.trim()}).filter(Boolean);const t=this.extractEmailFromAddress(e);return t?[t]:[]}cleanReplySubject(e){if(!e)return"";const t=/^(?:re:|回复:|答复:)\s*/i;let n,r=e.trim();do{n=r,r=r.replace(t,"").trim()}while(r!==n);return r}detectUnrepliedMessagesAdvanced(e,t,n,r){const s=[],i=[...e].sort((e,t)=>new Date(e.date).getTime()-new Date(t.date).getTime()),o=[...t].sort((e,t)=>new Date(e.date).getTime()-new Date(t.date).getTime());console.error(`[IMAP] Analyzing ${i.length} messages from sender and ${o.length} messages to sender`);const a=Math.min(i.length,100),c=i.slice(0,a);a<i.length&&console.error(`[IMAP] Performance optimization: Processing latest ${a} messages out of ${i.length} total`);for(let e=0;e<c.length;e++){const t=c[e];if(this.isMessageReplied(t,o,n))console.error(`[IMAP] Message UID ${t.uid} (${t.subject}) has been replied`);else if(s.push(t),console.error(`[IMAP] Message UID ${t.uid} (${t.subject}) marked as unreplied`),r&&s.length>=r){console.error(`[IMAP] Early termination: Found ${s.length} unreplied messages (limit: ${r}), stopping further analysis for performance`);break}}return s}isMessageReplied(e,t,n){const r=new Date(e.date),s=this.cleanReplySubject(e.subject||"").toLowerCase();if(t.filter(e=>{const t=new Date(e.date),n=this.cleanReplySubject(e.subject||"").toLowerCase();return t>r&&n===s&&n.length>0}).length>0)return console.error(`[IMAP] Found exact subject match reply for "${s}"`),!0;if(s.length>3&&t.filter(e=>{const t=new Date(e.date),n=this.cleanReplySubject(e.subject||"").toLowerCase();return t>r&&n.includes(s)&&n!==s}).length>0)return console.error(`[IMAP] Found thread-based reply for "${s}"`),!0;const i=new Date(r.getTime()+6048e5);return t.filter(t=>{const n=new Date(t.date);return n>r&&n<=i&&this.isLikelyReplyByContent(e,t)}).length>0&&(console.error(`[IMAP] Found time-window based reply for "${s}"`),!0)}isLikelyReplyByContent(e,t){const n=this.cleanReplySubject(e.subject||"").toLowerCase(),r=this.cleanReplySubject(t.subject||"").toLowerCase();if(n===r&&n.length>0)return!0;if(n.length>5&&r.includes(n))return!0;const s=n.split(/\s+/).filter(e=>e.length>3),i=r.split(/\s+/).filter(e=>e.length>3);return s.length>0&&i.length>0&&s.filter(e=>i.includes(e)).length>=Math.min(2,Math.ceil(s.length/2))}buildQuotedText(e,t,n){return`On ${t}, ${n} wrote:\n${e.split("\n").map(e=>`> ${e}`).join("\n")}`}escapeHtml(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}buildQuotedHtml(e,t,n){return`<div style="border-left: 3px solid #ccc; padding-left: 10px; margin-left: 10px; color: #666;">\n <p><strong>On ${this.escapeHtml(t)}, ${this.escapeHtml(n)} wrote:</strong></p>\n <div>${this.escapeHtml(e).replace(/\n/g,"<br>")}</div>\n </div>`}textToHtml(e){return e.replace(/\n/g,"<br>").replace(/ /g,"&nbsp;&nbsp;")}async findMessageInMultipleMailboxes(e){for(const t of w)try{await this.imapClient.openBox(t,!0);const n=await this.imapClient.getMessage(e);return console.error(`[Search] Found message in mailbox: ${t}`),n}catch(e){console.error(`[Search] Message not found in ${t}: ${e instanceof Error?e.message:String(e)}`)}return null}async ensureMailboxOpen(e="INBOX"){this.imapClient.getCurrentBox()||(console.error(`[IMAP] No mailbox currently open, opening ${e}`),await this.imapClient.openBox(e,!0))}async handleConnectAll(){const e=[];try{if(this.imapClient&&this.imapClient.isConnected()){const t=this.imapClient.getCurrentUsername(),n=g.IMAP.username;t!==n?(console.error(`[IMAP] User mismatch: current=${t}, config=${n}, reconnecting...`),await this.imapClient.disconnect(),await this.ensureIMAPConnection(),e.push("✅ IMAP: Reconnected with correct user")):e.push("ℹ️ IMAP: Already connected")}else await this.ensureIMAPConnection(),e.push("✅ IMAP: Connected successfully")}catch(t){e.push(`❌ IMAP: Connection failed - ${t instanceof Error?t.message:String(t)}`)}try{if(this.smtpClient&&this.smtpClient.isConnected()){const t=this.smtpClient.getCurrentUsername(),n=g.SMTP.username;t!==n?(console.error(`[SMTP] User mismatch: current=${t}, config=${n}, reconnecting...`),await this.smtpClient.disconnect(),await this.ensureSMTPConnection(),e.push("✅ SMTP: Reconnected with correct user")):e.push("ℹ️ SMTP: Already connected")}else 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,this.sentMailboxName=void 0,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: ${g.IMAP.host}:${g.IMAP.port} (TLS: ${g.IMAP.tls})`),console.error(`SMTP: ${g.SMTP.host}:${g.SMTP.port} (Secure: ${g.SMTP.secure})`),console.error(`User: ${g.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}}buildRawEmailMessage(e,t){const n=(new Date).toUTCString();let r="";if(r+=`Message-ID: ${t||`<${Date.now()}.${Math.random().toString(36)}@${g.IMAP.host}>`}\r\n`,r+=`Date: ${n}\r\n`,r+=`From: ${g.IMAP.username}\r\n`,r+=`To: ${Array.isArray(e.to)?e.to.join(", "):e.to}\r\n`,e.cc&&(r+=`Cc: ${Array.isArray(e.cc)?e.cc.join(", "):e.cc}\r\n`),e.bcc&&(r+=`Bcc: ${Array.isArray(e.bcc)?e.bcc.join(", "):e.bcc}\r\n`),r+=`Subject: ${e.subject}\r\n`,r+="MIME-Version: 1.0\r\n",e.html&&e.text){const t=`----=_Part_${Date.now()}_${Math.random().toString(36)}`;r+=`Content-Type: multipart/alternative; boundary="${t}"\r\n\r\n`,r+=`--${t}\r\n`,r+="Content-Type: text/plain; charset=utf-8\r\n",r+="Content-Transfer-Encoding: 8bit\r\n\r\n",r+=`${e.text}\r\n\r\n`,r+=`--${t}\r\n`,r+="Content-Type: text/html; charset=utf-8\r\n",r+="Content-Transfer-Encoding: 8bit\r\n\r\n",r+=`${e.html}\r\n\r\n`,r+=`--${t}--\r\n`}else e.html?(r+="Content-Type: text/html; charset=utf-8\r\n",r+="Content-Transfer-Encoding: 8bit\r\n\r\n",r+=`${e.html}\r\n`):(r+="Content-Type: text/plain; charset=utf-8\r\n",r+="Content-Transfer-Encoding: 8bit\r\n\r\n",r+=`${e.text||""}\r\n`);return r}async saveSentMessage(e,t){try{await this.ensureRequiredConnections(!0,!1);const n=await this.findSentMailbox();if(!n)return console.error("[Email] No sent folder found, skipping save to sent folder"),!1;const r=this.buildRawEmailMessage(e,t);return await this.imapClient.saveMessageToFolder(r,n),console.error("[Email] Message saved to sent folder successfully"),!0}catch(e){return console.error("[Email] Failed to save message to sent folder:",e instanceof Error?e.message:String(e)),!1}}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.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "MCP server for IMAP/SMTP email access with environment-based configuration",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",