listEmails
Retrieve and filter emails from your inbox using criteria like sender, date, attachments, or read status to manage messages efficiently.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| folder | No | INBOX | |
| limit | No | ||
| readStatus | No | all | |
| from | No | ||
| to | No | ||
| subject | No | ||
| fromDate | No | ||
| toDate | No | ||
| hasAttachments | No |
Implementation Reference
- src/tools/mail.ts:588-659 (registration)Registration of the listEmails MCP tool, including inline schema and handler.this.server.tool( "listEmails", { folder: z.string().default('INBOX'), limit: z.number().default(20), readStatus: z.enum(['read', 'unread', 'all']).default('all'), from: z.string().optional(), to: z.string().optional(), subject: z.string().optional(), fromDate: z.union([z.date(), z.string().datetime({ message: "fromDate 必须是有效的 ISO 8601 日期时间字符串或 Date 对象" })]).optional(), toDate: z.union([z.date(), z.string().datetime({ message: "toDate 必须是有效的 ISO 8601 日期时间字符串或 Date 对象" })]).optional(), hasAttachments: z.boolean().optional() }, async (params) => { try { // 处理日期字符串 const fromDate = typeof params.fromDate === 'string' ? new Date(params.fromDate) : params.fromDate; const toDate = typeof params.toDate === 'string' ? new Date(params.toDate) : params.toDate; const options: MailSearchOptions = { folder: params.folder, limit: params.limit, readStatus: params.readStatus, from: params.from, to: params.to, subject: params.subject, fromDate: fromDate, toDate: toDate, hasAttachments: params.hasAttachments }; const emails = await this.mailService.searchMails(options); // 转换为人类可读格式 if (emails.length === 0) { return { content: [ { type: "text", text: `在${params.folder}文件夹中没有找到符合条件的邮件。` } ] }; } let resultText = `在${params.folder}文件夹中找到了${emails.length}封邮件:\n\n`; emails.forEach((email, index) => { const fromStr = email.from.map(f => f.name ? `${f.name} <${f.address}>` : f.address).join(', '); const date = email.date.toLocaleString(); const status = email.isRead ? '已读' : '未读'; const attachmentInfo = email.hasAttachments ? '📎' : ''; resultText += `${index + 1}. [${status}] ${attachmentInfo} 来自: ${fromStr}\n`; resultText += ` 主题: ${email.subject}\n`; resultText += ` 时间: ${date}\n`; resultText += ` UID: ${email.uid}\n\n`; }); resultText += `使用 getEmailDetail 工具并提供 UID 可以查看邮件详情。`; return { content: [ { type: "text", text: resultText } ] }; } catch (error) { return { content: [ { type: "text", text: `获取邮件列表时发生错误: ${error instanceof Error ? error.message : String(error)}` } ] }; } } );
- src/tools/mail.ts:601-658 (handler)The main handler function for listEmails tool that processes parameters, calls MailService.searchMails, and formats the human-readable response.async (params) => { try { // 处理日期字符串 const fromDate = typeof params.fromDate === 'string' ? new Date(params.fromDate) : params.fromDate; const toDate = typeof params.toDate === 'string' ? new Date(params.toDate) : params.toDate; const options: MailSearchOptions = { folder: params.folder, limit: params.limit, readStatus: params.readStatus, from: params.from, to: params.to, subject: params.subject, fromDate: fromDate, toDate: toDate, hasAttachments: params.hasAttachments }; const emails = await this.mailService.searchMails(options); // 转换为人类可读格式 if (emails.length === 0) { return { content: [ { type: "text", text: `在${params.folder}文件夹中没有找到符合条件的邮件。` } ] }; } let resultText = `在${params.folder}文件夹中找到了${emails.length}封邮件:\n\n`; emails.forEach((email, index) => { const fromStr = email.from.map(f => f.name ? `${f.name} <${f.address}>` : f.address).join(', '); const date = email.date.toLocaleString(); const status = email.isRead ? '已读' : '未读'; const attachmentInfo = email.hasAttachments ? '📎' : ''; resultText += `${index + 1}. [${status}] ${attachmentInfo} 来自: ${fromStr}\n`; resultText += ` 主题: ${email.subject}\n`; resultText += ` 时间: ${date}\n`; resultText += ` UID: ${email.uid}\n\n`; }); resultText += `使用 getEmailDetail 工具并提供 UID 可以查看邮件详情。`; return { content: [ { type: "text", text: resultText } ] }; } catch (error) { return { content: [ { type: "text", text: `获取邮件列表时发生错误: ${error instanceof Error ? error.message : String(error)}` } ] }; } }
- src/tools/mail.ts:590-600 (schema)Input schema validation using Zod for listEmails tool parameters.{ folder: z.string().default('INBOX'), limit: z.number().default(20), readStatus: z.enum(['read', 'unread', 'all']).default('all'), from: z.string().optional(), to: z.string().optional(), subject: z.string().optional(), fromDate: z.union([z.date(), z.string().datetime({ message: "fromDate 必须是有效的 ISO 8601 日期时间字符串或 Date 对象" })]).optional(), toDate: z.union([z.date(), z.string().datetime({ message: "toDate 必须是有效的 ISO 8601 日期时间字符串或 Date 对象" })]).optional(), hasAttachments: z.boolean().optional() },
- src/tools/mail-service.ts:214-368 (helper)Core helper method MailService.searchMails that implements the IMAP search logic, fetching and parsing emails.async searchMails(options: MailSearchOptions = {}): Promise<MailItem[]> { await this.connectImap(); const folder = options.folder || 'INBOX'; const limit = options.limit || 20; return new Promise((resolve, reject) => { this.imapClient.openBox(folder, false, (err, box) => { if (err) { reject(err); return; } // 构建搜索条件 const criteria: any[] = []; if (options.readStatus === 'read') { criteria.push('SEEN'); } else if (options.readStatus === 'unread') { criteria.push('UNSEEN'); } if (options.fromDate) { criteria.push(['SINCE', options.fromDate]); } if (options.toDate) { criteria.push(['BEFORE', options.toDate]); } if (options.from) { criteria.push(['FROM', options.from]); } if (options.to) { criteria.push(['TO', options.to]); } if (options.subject) { criteria.push(['SUBJECT', options.subject]); } if (criteria.length === 0) { criteria.push('ALL'); } // 执行搜索 this.imapClient.search(criteria, (err, uids) => { if (err) { reject(err); return; } if (uids.length === 0) { resolve([]); return; } // 限制结果数量 const limitedUids = uids.slice(-Math.min(limit, uids.length)); // 获取邮件详情 const fetch = this.imapClient.fetch(limitedUids, { bodies: ['HEADER', 'TEXT'], struct: true, envelope: true, size: true, markSeen: false, }); const messages: MailItem[] = []; fetch.on('message', (msg, seqno) => { const message: Partial<MailItem> = { id: '', uid: 0, folder, flags: [], subject: '', from: [], to: [], date: new Date(), isRead: false, hasAttachments: false, size: 0, }; msg.on('body', (stream, info) => { let buffer = ''; stream.on('data', (chunk) => { buffer += chunk.toString('utf8'); }); stream.once('end', () => { if (info.which === 'HEADER') { const parsed = IMAP.parseHeader(buffer); message.subject = parsed.subject?.[0] || ''; message.from = this.parseAddressList(parsed.from); message.to = this.parseAddressList(parsed.to); message.cc = this.parseAddressList(parsed.cc); if (parsed.date && parsed.date[0]) { message.date = new Date(parsed.date[0]); } } else if (info.which === 'TEXT') { const readable = new Readable(); readable.push(buffer); readable.push(null); simpleParser(readable).then((parsed) => { message.textBody = parsed.text || undefined; message.htmlBody = parsed.html || undefined; message.attachments = parsed.attachments.map(att => ({ filename: att.filename || 'unknown', contentType: att.contentType, size: att.size, })); message.hasAttachments = parsed.attachments.length > 0; }).catch(err => { console.error('解析邮件内容错误:', err); }); } }); }); msg.once('attributes', (attrs) => { message.uid = attrs.uid; message.id = attrs.uid.toString(); message.flags = attrs.flags; message.isRead = attrs.flags.includes('\\Seen'); message.size = attrs.size || 0; // 检查是否有附件 if (attrs.struct) { message.hasAttachments = this.checkHasAttachments(attrs.struct); } }); msg.once('end', () => { messages.push(message as MailItem); }); }); fetch.once('error', (err) => { reject(err); }); fetch.once('end', () => { resolve(messages); }); }); }); }); }
- src/tools/mail-service.ts:49-59 (schema)TypeScript interface MailSearchOptions defining the structure for search parameters used by listEmails.export interface MailSearchOptions { folder?: string; readStatus?: 'read' | 'unread' | 'all'; fromDate?: Date; toDate?: Date; from?: string; to?: string; subject?: string; hasAttachments?: boolean; limit?: number; }