parse_eml_headers
Extract MIME headers from .eml files to analyze email threads, retrieving message IDs, subjects, sender/recipient details, and dates for correlation.
Instructions
Extract MIME headers from an .eml file for email thread correlation. Returns message_id, in_reply_to, references (array), subject, from, to, cc, and date. Handles CRLF/LF line endings, folded headers, and RFC 2047 encoded words in Subject/From/To. Input: { "filePath": "/path/to/email.eml" }
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| filePath | Yes | Absolute path to the .eml file to parse |
Implementation Reference
- The main function `parseEmlHeaders` that reads the .eml file and extracts headers using helper functions.
const parseEmlHeaders = async (args: Record<string, unknown>): Promise<EmlHeadersResult> => { const filePath = args.filePath as string | undefined; if (!filePath || typeof filePath !== "string") { return { success: false, error: "filePath parameter is required and must be a string" }; } if (!existsSync(filePath)) { return { success: false, filePath, error: `File not found: ${filePath}` }; } let rawContent: string; try { const HEADER_READ_LIMIT = 65536; const fd = await new Promise<Buffer>((resolve, reject) => { const stream = createReadStream(filePath, { start: 0, end: HEADER_READ_LIMIT - 1 }); const chunks: Buffer[] = []; stream.on("data", (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk as string))); stream.on("end", () => resolve(Buffer.concat(chunks))); stream.on("error", reject); }); rawContent = fd.toString("utf-8"); } catch (err) { return { success: false, filePath, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}`, }; } const crlf = rawContent.indexOf("\r\n\r\n"); const lf = rawContent.indexOf("\n\n"); let headerSection: string; if (crlf !== -1 && (lf === -1 || crlf < lf)) { headerSection = rawContent.slice(0, crlf); } else if (lf !== -1) { headerSection = rawContent.slice(0, lf); } else { headerSection = rawContent; } const headers = parseHeaders(headerSection); const get = (name: string): string | null => { const val = headers.get(name); return val !== undefined ? decodeEncodedWords(val.trim()) : null; }; const referencesRaw = get("references"); return { success: true, filePath, messageId: get("message-id"), inReplyTo: get("in-reply-to"), references: referencesRaw ? parseReferences(referencesRaw) : [], subject: get("subject"), from: get("from"), to: get("to"), cc: get("cc"), date: get("date"), }; }; - src/tools/custom/parse-eml-headers.ts:168-187 (registration)The `parseEmlHeadersTool` MCP tool definition which links the `parse_eml_headers` name to the `parseEmlHeaders` handler.
export const parseEmlHeadersTool: McpTool = { name: "parse_eml_headers", description: "Extract MIME headers from an .eml file for email thread correlation. " + "Returns message_id, in_reply_to, references (array), subject, from, to, cc, and date. " + "Handles CRLF/LF line endings, folded headers, and RFC 2047 encoded words in Subject/From/To. " + 'Input: { "filePath": "/path/to/email.eml" }', inputSchema: { type: "object" as const, properties: { filePath: { type: "string", description: "Absolute path to the .eml file to parse", }, }, required: ["filePath"], additionalProperties: false, }, run: parseEmlHeaders, }; - The `parseHeaders` helper function used to parse the raw header section of an email file.
function parseHeaders(headerSection: string): Map<string, string> { const headers = new Map<string, string>(); const normalised = headerSection.replace(/\r\n/g, "\n"); const lines = normalised.split("\n"); let currentName: string | null = null; let currentValue = ""; const flush = (): void => { if (currentName !== null) { const unfolded = currentValue.replace(/\n[ \t]+/g, " ").trim(); const key = currentName.toLowerCase(); if (headers.has(key)) { headers.set(key, headers.get(key) + "\n" + unfolded); } else { headers.set(key, unfolded); } } }; for (const line of lines) { if (line === "") break; if (line[0] === " " || line[0] === "\t") { if (currentName !== null) { currentValue += "\n" + line; } } else { flush(); const colonIdx = line.indexOf(":"); if (colonIdx > 0) { currentName = line.slice(0, colonIdx).trim(); currentValue = line.slice(colonIdx + 1); } else { currentName = null; currentValue = ""; } } } flush(); return headers; }