get_unsubscribe_link
Extract the unsubscribe link from an email by checking the List-Unsubscribe header, then scanning the message body if the header is absent.
Instructions
Extract unsubscribe URLs from a message — checks the List-Unsubscribe header first (reliable), then scans the plain-text body as a fallback.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| message_id | Yes | RFC message-id (with or without angle brackets) |
Implementation Reference
- src/tools/get_unsubscribe_link.ts:66-91 (handler)The register function that implements the get_unsubscribe_link tool. It receives a message_id, finds the message via AppleScript, extracts the raw source + body content, and parses out List-Unsubscribe header URLs and body URLs that look like unsubscribe links.
export function register(server: McpServer): void { server.tool( "get_unsubscribe_link", "Extract unsubscribe URLs from a message — checks the List-Unsubscribe header first (reliable), then scans the plain-text body as a fallback.", schema, { title: "Get Unsubscribe Link", readOnlyHint: true, destructiveHint: false }, async ({ message_id }) => { const bareId = message_id.replace(/^<|>$/g, ""); const raw = await runAppleScript({ script: SCRIPT, args: { theMsgId: bareId }, timeoutMs: 30_000, }); if (raw === "NOTFOUND") { return { content: [{ type: "text", text: `No message found with id ${message_id}` }], isError: true, }; } const urls = extractUnsubscribeUrls(raw); return { content: [{ type: "text", text: JSON.stringify(urls, null, 2) }], }; }, ); } - Input schema for the tool: requires a 'message_id' string (RFC message-id, with or without angle brackets).
const schema = { message_id: z.string().describe("RFC message-id (with or without angle brackets)"), }; - src/server.ts:16-36 (registration)Import of the get_unsubscribe_link registration function.
import { register as registerGetUnsubscribeLink } from "./tools/get_unsubscribe_link.js"; import { register as registerListSenders } from "./tools/list_senders.js"; import { register as registerEmptyMailbox } from "./tools/empty_mailbox.js"; const server = new McpServer({ name: "mail-app-mcp", version: "1.0.0", }); registerSearch(server); registerRead(server); registerAccounts(server); registerListRecent(server); registerSend(server); registerReply(server); registerFlags(server); registerMove(server); registerTrash(server); registerCreateMailbox(server); registerBulkMarkRead(server); registerGetUnsubscribeLink(server); - src/server.ts:36-36 (registration)Registration call: registerGetUnsubscribeLink(server) wires the tool into the MCP server.
registerGetUnsubscribeLink(server); - Helper function extractUnsubscribeUrls that parses the raw source to find List-Unsubscribe header URLs and scans the plain-text body for URLs containing unsubscribe/opt-out keywords.
export function extractUnsubscribeUrls(raw: string): { header: string[]; body: string[] } { const splitIdx = raw.indexOf(SPLIT); const headers = splitIdx >= 0 ? raw.slice(0, splitIdx) : raw; const body = splitIdx >= 0 ? raw.slice(splitIdx + SPLIT.length) : ""; const header: string[] = []; const bodyUrls: string[] = []; // List-Unsubscribe header may be folded (continuation lines start with whitespace) const match = headers.match(/^List-Unsubscribe:[ \t]*((?:[^\r\n]|\r?\n[ \t])*)/im); if (match) { const value = (match[1] ?? "").replace(/\r?\n[ \t]+/g, " "); for (const m of value.matchAll(/<([^>]+)>/g)) { if (m[1]) header.push(m[1]); } } // Scan plain-text body for URLs that look like unsubscribe links const urlRe = /https?:\/\/[^\s<>"')\]]+/g; for (const m of body.matchAll(urlRe)) { const url = m[0].replace(/[.,;]+$/, ""); if (/unsubscribe|opt.?out|optout|remove/i.test(url) && !header.includes(url) && !bodyUrls.includes(url)) { bodyUrls.push(url); } } return { header, body: bodyUrls }; }