Skip to main content
Glama

getAttachment

Retrieve email attachments from a specified folder by providing the email UID and attachment index, then save them to a file for access.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
uidYes
folderNoINBOX
attachmentIndexYes
saveToFileNo

Implementation Reference

  • Primary handler function that implements the core logic for retrieving email attachments via IMAP. It fetches the email structure, identifies the specified attachment, and downloads its content.
    async getAttachment(uid: number, folder: string = 'INBOX', attachmentIndex: number): Promise<{ filename: string; content: Buffer; contentType: string } | null> { await this.connectImap(); console.log(`正在获取UID ${uid} 的第 ${attachmentIndex} 个附件...`); return new Promise((resolve, reject) => { this.imapClient.openBox(folder, true, (err) => { if (err) { console.error(`打开文件夹 ${folder} 失败:`, err); reject(err); return; } const f = this.imapClient.fetch(`${uid}`, { bodies: '', struct: true }); let attachmentInfo: { partID: string; filename: string; contentType: string } | null = null; f.on('message', (msg, seqno) => { msg.on('body', (stream, info) => { // 这个事件处理器只是为了确保消息体被处理 stream.on('data', () => {}); stream.on('end', () => {}); }); msg.once('attributes', (attrs) => { try { const struct = attrs.struct; const attachments = this.findAttachmentParts(struct); if (attachments.length <= attachmentIndex) { console.log(`附件索引 ${attachmentIndex} 超出范围,附件总数: ${attachments.length}`); resolve(null); return; } attachmentInfo = attachments[attachmentIndex]; console.log(`找到附件信息:`, attachmentInfo); } catch (error) { console.error(`解析附件结构时出错:`, error); reject(error); } }); msg.once('end', () => { if (!attachmentInfo) { console.log(`未找到附件或附件索引无效`); resolve(null); return; } // 获取附件内容 const attachmentFetch = this.imapClient.fetch(`${uid}`, { bodies: [attachmentInfo.partID], struct: true }); let buffer = Buffer.alloc(0); attachmentFetch.on('message', (msg, seqno) => { msg.on('body', (stream, info) => { stream.on('data', (chunk) => { buffer = Buffer.concat([buffer, chunk]); }); stream.once('end', () => { console.log(`附件内容下载完成,大小: ${buffer.length} 字节`); }); }); msg.once('end', () => { console.log(`附件消息处理完成`); }); }); attachmentFetch.once('error', (err) => { console.error(`获取附件内容时出错:`, err); reject(err); }); attachmentFetch.once('end', () => { console.log(`附件获取流程结束`); resolve({ filename: attachmentInfo!.filename, content: buffer, contentType: attachmentInfo!.contentType }); }); }); }); f.once('error', (err) => { console.error(`获取邮件时出错:`, err); reject(err); }); f.once('end', () => { if (!attachmentInfo) { console.log(`未找到附件或结构中没有附件`); resolve(null); } }); }); }); }
  • MCP tool registration for 'getAttachment' including input schema validation with Zod and wrapper handler that invokes the MailService method and handles response (saving to file or displaying).
    this.server.tool( "getAttachment", { uid: z.number(), folder: z.string().default('INBOX'), attachmentIndex: z.number(), saveToFile: z.boolean().default(true) }, async (params) => { try { const attachment = await this.mailService.getAttachment( params.uid, params.folder, params.attachmentIndex ); if (!attachment) { return { content: [ { type: "text", text: `未找到UID为${params.uid}的邮件的第${params.attachmentIndex}个附件` } ] }; } // 根据是否保存到文件处理附件 if (params.saveToFile) { // 创建附件保存目录 const downloadDir = path.join(process.cwd(), 'downloads'); if (!fs.existsSync(downloadDir)) { fs.mkdirSync(downloadDir, { recursive: true }); } // 生成安全的文件名(去除非法字符) const safeFilename = attachment.filename.replace(/[/\\?%*:|"<>]/g, '-'); const filePath = path.join(downloadDir, safeFilename); // 写入文件 fs.writeFileSync(filePath, attachment.content); return { content: [ { type: "text", text: `附件 "${attachment.filename}" 已下载保存至 ${filePath}\n类型: ${attachment.contentType}\n大小: ${Math.round(attachment.content.length / 1024)} KB` } ] }; } else { // 根据内容类型处理内容 if (attachment.contentType.startsWith('text/') || attachment.contentType === 'application/json') { // 文本文件显示内容 const textContent = attachment.content.toString('utf-8'); return { content: [ { type: "text", text: `📎 附件 "${attachment.filename}" (${attachment.contentType})\n\n${textContent.substring(0, 10000)}${textContent.length > 10000 ? '\n\n[内容过长,已截断]' : ''}` } ] }; } else if (attachment.contentType.startsWith('image/')) { // 图片文件提供Base64编码 const base64Content = attachment.content.toString('base64'); return { content: [ { type: "text", text: `📎 图片附件 "${attachment.filename}" (${attachment.contentType})\n大小: ${Math.round(attachment.content.length / 1024)} KB\n\n[图片内容已转为Base64编码,可用于在线预览]` } ] }; } else { // 其他二进制文件 return { content: [ { type: "text", text: `📎 二进制附件 "${attachment.filename}" (${attachment.contentType})\n大小: ${Math.round(attachment.content.length / 1024)} KB\n\n[二进制内容无法直接显示]` } ] }; } } } catch (error) { return { content: [ { type: "text", text: `获取附件时发生错误: ${error instanceof Error ? error.message : String(error)}` } ] }; } } );
  • Input schema definition using Zod for the getAttachment tool parameters: uid (required number), folder (string, default 'INBOX'), attachmentIndex (required number), saveToFile (boolean, default true).
    uid: z.number(), folder: z.string().default('INBOX'), attachmentIndex: z.number(), saveToFile: z.boolean().default(true) },
  • Helper method used by getAttachment to parse email MIME structure and find all attachment parts with their identifiers and metadata.
    private findAttachmentParts(struct: any[], prefix = ''): { partID: string; filename: string; contentType: string }[] { const attachments: { partID: string; filename: string; contentType: string }[] = []; if (!struct || !Array.isArray(struct)) return attachments; const processStruct = (s: any, partID = '') => { if (Array.isArray(s)) { // 多部分结构 if (s[0] && typeof s[0] === 'object' && s[0].partID) { // 这是一个具体的部分 if (s[0].disposition && (s[0].disposition.type.toLowerCase() === 'attachment' || s[0].disposition.type.toLowerCase() === 'inline')) { let filename = ''; if (s[0].disposition.params && s[0].disposition.params.filename) { filename = s[0].disposition.params.filename; } else if (s[0].params && s[0].params.name) { filename = s[0].params.name; } const contentType = s[0].type + '/' + s[0].subtype; if (filename) { attachments.push({ partID: s[0].partID, filename: filename, contentType: contentType }); } } } else { // 遍历数组中的每个元素 for (let i = 0; i < s.length; i++) { const newPrefix = partID ? `${partID}.${i + 1}` : `${i + 1}`; if (Array.isArray(s[i])) { processStruct(s[i], newPrefix); } else if (typeof s[i] === 'object') { // 可能是一个部分定义 if (s[i].disposition && (s[i].disposition.type.toLowerCase() === 'attachment' || s[i].disposition.type.toLowerCase() === 'inline')) { let filename = ''; if (s[i].disposition.params && s[i].disposition.params.filename) { filename = s[i].disposition.params.filename; } else if (s[i].params && s[i].params.name) { filename = s[i].params.name; } const contentType = s[i].type + '/' + s[i].subtype; if (filename) { attachments.push({ partID: newPrefix, filename: filename, contentType: contentType }); } } } } } } }; processStruct(struct, prefix); return attachments; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/shuakami/mcp-mail'

If you have feedback or need assistance with the MCP directory API, please join our Discord server