getEmailDetail
Retrieve detailed content from a specific email message by providing its unique identifier and folder location, enabling AI assistants to access and process email information for various operations.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| uid | Yes | ||
| folder | No | INBOX | |
| contentRange | No |
Implementation Reference
- src/tools/mail.ts:722-847 (registration)Registration of the getEmailDetail MCP tool, including input schema and handler function that delegates to MailService.getMailDetail and formats the response for the user.this.server.tool( "getEmailDetail", { uid: z.number(), folder: z.string().default('INBOX'), contentRange: z.object({ start: z.number().default(0), end: z.number().default(2000) }).optional() }, async ({ uid, folder, contentRange }) => { try { // 对于QQ邮箱的特殊处理,先尝试获取邮件详情 const numericUid = Number(uid); let email = await this.mailService.getMailDetail(numericUid, folder); // 如果正常获取失败,尝试通过搜索来获取指定UID的邮件 if (!email) { console.log(`通过常规方法获取邮件详情失败,尝试使用搜索方法获取UID为${numericUid}的邮件`); const searchResults = await this.mailService.searchMails({ folder: folder, limit: 50 // 搜索更多邮件以提高找到目标的可能性 }); // 从搜索结果中找到指定UID的邮件 const foundEmail = searchResults.find(e => e.uid === numericUid); if (foundEmail) { console.log(`在搜索结果中找到了UID为${numericUid}的邮件`); email = foundEmail; // 尝试获取邮件正文(如果没有) if (!email.textBody && !email.htmlBody) { console.log(`邮件没有正文内容,尝试单独获取正文`); try { // 这里可以添加额外的尝试获取正文的逻辑 // ... } catch (e) { console.error('获取邮件正文时出错:', e); } } } } if (!email) { return { content: [ { type: "text", text: `未找到UID为${numericUid}的邮件` } ] }; } // 转换为人类可读格式 const fromStr = email.from.map(f => f.name ? `${f.name} <${f.address}>` : f.address).join(', '); const toStr = email.to.map(t => t.name ? `${t.name} <${t.address}>` : t.address).join(', '); const ccStr = email.cc ? email.cc.map(c => c.name ? `${c.name} <${c.address}>` : c.address).join(', ') : ''; const date = email.date.toLocaleString(); const status = email.isRead ? '已读' : '未读'; let resultText = `📧 邮件详情 (UID: ${email.uid})\n\n`; resultText += `主题: ${email.subject}\n`; resultText += `发件人: ${fromStr}\n`; resultText += `收件人: ${toStr}\n`; if (ccStr) resultText += `抄送: ${ccStr}\n`; resultText += `日期: ${date}\n`; resultText += `状态: ${status}\n`; resultText += `文件夹: ${email.folder}\n`; if (email.hasAttachments && email.attachments && email.attachments.length > 0) { resultText += `\n📎 附件 (${email.attachments.length}个):\n`; email.attachments.forEach((att, index) => { const sizeInKB = Math.round(att.size / 1024); resultText += `${index + 1}. ${att.filename} (${sizeInKB} KB, ${att.contentType})\n`; }); } // 获取邮件内容 let content = ''; if (email.textBody) { content = email.textBody; } else if (email.htmlBody) { // 简单的HTML转文本处理 content = '(HTML内容,显示纯文本版本)\n\n' + email.htmlBody .replace(/<br\s*\/?>/gi, '\n') .replace(/<\/p>/gi, '\n\n') .replace(/<[^>]*>/g, ''); } else { content = '(邮件没有文本内容或内容无法获取)\n\n' + '可能原因:\n' + '1. QQ邮箱IMAP访问限制\n' + '2. 邮件内容格式特殊\n' + '建议直接在QQ邮箱网页或客户端查看完整内容'; } // 计算内容总长度 const totalLength = content.length; // 设置默认范围 const start = contentRange?.start || 0; const end = Math.min(contentRange?.end || 2000, totalLength); // 根据范围截取内容 const selectedContent = content.substring(start, end); resultText += `\n📄 内容 (${start+1}-${end}/${totalLength}字符):\n\n`; resultText += selectedContent; // 如果有更多内容,添加提示 if (end < totalLength) { resultText += `\n\n[...]\n\n(内容过长,仅显示前${end}个字符。使用contentRange参数可查看更多内容,例如查看${end+1}-${Math.min(end+2000, totalLength)}范围:contentRange.start=${end}, contentRange.end=${Math.min(end+2000, totalLength)})`; } return { content: [ { type: "text", text: resultText } ] }; } catch (error) { return { content: [ { type: "text", text: `获取邮件详情时发生错误: ${error instanceof Error ? error.message : String(error)}` } ] }; } } );
- src/tools/mail.ts:724-731 (schema)Zod schema for getEmailDetail tool inputs: uid (required number), folder (string default 'INBOX'), optional contentRange for partial content fetching.{ uid: z.number(), folder: z.string().default('INBOX'), contentRange: z.object({ start: z.number().default(0), end: z.number().default(2000) }).optional() },
- src/tools/mail.ts:732-846 (handler)The executing handler logic for getEmailDetail: fetches email via MailService, handles fallback search, formats details and body content with optional range, returns formatted text.async ({ uid, folder, contentRange }) => { try { // 对于QQ邮箱的特殊处理,先尝试获取邮件详情 const numericUid = Number(uid); let email = await this.mailService.getMailDetail(numericUid, folder); // 如果正常获取失败,尝试通过搜索来获取指定UID的邮件 if (!email) { console.log(`通过常规方法获取邮件详情失败,尝试使用搜索方法获取UID为${numericUid}的邮件`); const searchResults = await this.mailService.searchMails({ folder: folder, limit: 50 // 搜索更多邮件以提高找到目标的可能性 }); // 从搜索结果中找到指定UID的邮件 const foundEmail = searchResults.find(e => e.uid === numericUid); if (foundEmail) { console.log(`在搜索结果中找到了UID为${numericUid}的邮件`); email = foundEmail; // 尝试获取邮件正文(如果没有) if (!email.textBody && !email.htmlBody) { console.log(`邮件没有正文内容,尝试单独获取正文`); try { // 这里可以添加额外的尝试获取正文的逻辑 // ... } catch (e) { console.error('获取邮件正文时出错:', e); } } } } if (!email) { return { content: [ { type: "text", text: `未找到UID为${numericUid}的邮件` } ] }; } // 转换为人类可读格式 const fromStr = email.from.map(f => f.name ? `${f.name} <${f.address}>` : f.address).join(', '); const toStr = email.to.map(t => t.name ? `${t.name} <${t.address}>` : t.address).join(', '); const ccStr = email.cc ? email.cc.map(c => c.name ? `${c.name} <${c.address}>` : c.address).join(', ') : ''; const date = email.date.toLocaleString(); const status = email.isRead ? '已读' : '未读'; let resultText = `📧 邮件详情 (UID: ${email.uid})\n\n`; resultText += `主题: ${email.subject}\n`; resultText += `发件人: ${fromStr}\n`; resultText += `收件人: ${toStr}\n`; if (ccStr) resultText += `抄送: ${ccStr}\n`; resultText += `日期: ${date}\n`; resultText += `状态: ${status}\n`; resultText += `文件夹: ${email.folder}\n`; if (email.hasAttachments && email.attachments && email.attachments.length > 0) { resultText += `\n📎 附件 (${email.attachments.length}个):\n`; email.attachments.forEach((att, index) => { const sizeInKB = Math.round(att.size / 1024); resultText += `${index + 1}. ${att.filename} (${sizeInKB} KB, ${att.contentType})\n`; }); } // 获取邮件内容 let content = ''; if (email.textBody) { content = email.textBody; } else if (email.htmlBody) { // 简单的HTML转文本处理 content = '(HTML内容,显示纯文本版本)\n\n' + email.htmlBody .replace(/<br\s*\/?>/gi, '\n') .replace(/<\/p>/gi, '\n\n') .replace(/<[^>]*>/g, ''); } else { content = '(邮件没有文本内容或内容无法获取)\n\n' + '可能原因:\n' + '1. QQ邮箱IMAP访问限制\n' + '2. 邮件内容格式特殊\n' + '建议直接在QQ邮箱网页或客户端查看完整内容'; } // 计算内容总长度 const totalLength = content.length; // 设置默认范围 const start = contentRange?.start || 0; const end = Math.min(contentRange?.end || 2000, totalLength); // 根据范围截取内容 const selectedContent = content.substring(start, end); resultText += `\n📄 内容 (${start+1}-${end}/${totalLength}字符):\n\n`; resultText += selectedContent; // 如果有更多内容,添加提示 if (end < totalLength) { resultText += `\n\n[...]\n\n(内容过长,仅显示前${end}个字符。使用contentRange参数可查看更多内容,例如查看${end+1}-${Math.min(end+2000, totalLength)}范围:contentRange.start=${end}, contentRange.end=${Math.min(end+2000, totalLength)})`; } return { content: [ { type: "text", text: resultText } ] }; } catch (error) { return { content: [ { type: "text", text: `获取邮件详情时发生错误: ${error instanceof Error ? error.message : String(error)}` } ] }; } }
- src/tools/mail-service.ts:373-513 (helper)Core helper method MailService.getMailDetail that performs IMAP fetch and parsing to retrieve full email details including body and attachments.async getMailDetail(uid: number | string, folder: string = 'INBOX'): Promise<MailItem | null> { await this.connectImap(); // 确保 uid 为数字类型 const numericUid = typeof uid === 'string' ? parseInt(uid, 10) : uid; return new Promise((resolve, reject) => { this.imapClient.openBox(folder, false, (err) => { if (err) { reject(err); return; } const fetch = this.imapClient.fetch([numericUid], { bodies: '', struct: true, markSeen: false, }); let mailItem: MailItem | null = null; let attributes: any = null; let bodyParsed = false; let endReceived = false; // 检查是否所有处理都已完成并可以返回结果 const checkAndResolve = () => { if (bodyParsed && endReceived) { // 如果有属性数据但mailItem还没设置上,则现在设置 if (attributes && mailItem) { mailItem.flags = attributes.flags; mailItem.isRead = attributes.flags.includes('\\Seen'); mailItem.size = attributes.size || 0; } resolve(mailItem); } }; fetch.on('message', (msg) => { msg.on('body', (stream) => { // 创建一个可读流缓冲区 let buffer = ''; stream.on('data', (chunk) => { buffer += chunk.toString('utf8'); }); stream.once('end', () => { // 使用simpleParser解析邮件内容 const readable = new Readable(); readable.push(buffer); readable.push(null); simpleParser(readable).then((parsed: ParsedMail) => { // 处理发件人信息 const from: EmailAddress[] = []; if (parsed.from && 'value' in parsed.from) { from.push(...(parsed.from.value.map(addr => ({ name: addr.name || undefined, address: addr.address || '', })))); } // 处理收件人信息 const to: EmailAddress[] = []; if (parsed.to && 'value' in parsed.to) { to.push(...(parsed.to.value.map(addr => ({ name: addr.name || undefined, address: addr.address || '', })))); } // 处理抄送人信息 const cc: EmailAddress[] = []; if (parsed.cc && 'value' in parsed.cc) { cc.push(...(parsed.cc.value.map(addr => ({ name: addr.name || undefined, address: addr.address || '', })))); } mailItem = { id: numericUid.toString(), uid: numericUid, subject: parsed.subject || '', from, to, cc: cc.length > 0 ? cc : undefined, date: parsed.date || new Date(), isRead: false, // 将通过attributes更新 hasAttachments: parsed.attachments.length > 0, attachments: parsed.attachments.map(att => ({ filename: att.filename || 'unknown', contentType: att.contentType, size: att.size, })), textBody: parsed.text || undefined, htmlBody: parsed.html || undefined, size: 0, // 将通过attributes更新 folder, }; // 如果已经接收到属性,现在应用它们 if (attributes) { mailItem.flags = attributes.flags; mailItem.isRead = attributes.flags.includes('\\Seen'); mailItem.size = attributes.size || 0; } bodyParsed = true; checkAndResolve(); }).catch(err => { console.error('解析邮件详情错误:', err); reject(err); }); }); }); msg.once('attributes', (attrs) => { attributes = attrs; if (mailItem) { mailItem.flags = attrs.flags; mailItem.isRead = attrs.flags.includes('\\Seen'); mailItem.size = attrs.size || 0; } }); }); fetch.once('error', (err) => { reject(err); }); fetch.once('end', () => { endReceived = true; // 如果邮件没有内容,或者处理过程中出现问题,尝试确保至少返回空结果 if (!bodyParsed && !mailItem) { console.log(`没有找到UID为${numericUid}的邮件或邮件内容为空`); } checkAndResolve(); }); }); }); }
- src/tools/mail-service.ts:62-78 (schema)TypeScript interface MailItem defining the structure of email data returned by getMailDetail.export interface MailItem { id: string; uid: number; subject: string; from: { name?: string; address: string }[]; to: { name?: string; address: string }[]; cc?: { name?: string; address: string }[]; date: Date; isRead: boolean; hasAttachments: boolean; attachments?: { filename: string; contentType: string; size: number }[]; textBody?: string; htmlBody?: string; flags?: string[]; size: number; folder: string; }