Download Media
download_mediaDownload media from a WhatsApp message and write it to disk. Returns file path, mimetype, size, and message ID without raw base64 to prevent context overflow.
Instructions
Download media from a WhatsApp message and write it to disk. Returns { path, mimetype, size, messageId } — NOT raw base64 (prevents context overflow). File is written to /tmp/mcp-evolution-media/-.. Caller is responsible for cleanup (rm the file when done).
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| message | Yes | Message object containing the media | |
| convertToMp4 | No | Convert audio to mp4 format (forwarded to Evolution) |
Implementation Reference
- src/tools/download-media.ts:29-81 (handler)The main handler function 'registerDownloadMedia' that registers the 'download_media' tool. It sends a POST request to Evolution API's /chat/getBase64FromMediaMessage endpoint, decodes the returned base64 media data, and writes it to disk at /tmp/mcp-evolution-media/. Returns { path, mimetype, size, messageId }.
export function registerDownloadMedia(server: McpServer, client: EvolutionClient): void { server.registerTool( "download_media", { title: "Download Media", description: "Download media from a WhatsApp message and write it to disk. " + "Returns { path, mimetype, size, messageId } — NOT raw base64 (prevents context overflow). " + "File is written to /tmp/mcp-evolution-media/<instance>-<messageId>.<ext>. " + "Caller is responsible for cleanup (rm the file when done).", inputSchema: schema, }, async (args) => { try { const payload: Record<string, unknown> = { message: args.message }; if (args.convertToMp4 !== undefined) payload["convertToMp4"] = args.convertToMp4; const data = await client.post( `/chat/getBase64FromMediaMessage/${client.instanceName}`, payload ) as EvolutionMediaResponse; const base64 = data.base64; if (!base64) { return { isError: true, content: [{ type: "text" as const, text: "Evolution returned no base64 data for this message." }], }; } const mimetype = data.mimetype ?? data.mediaType ?? "application/octet-stream"; const ext = mimeToExt(mimetype); const messageId = args.message.key.id; const filename = `${client.instanceName}-${messageId}.${ext}`; const filePath = path.join(MEDIA_DIR, filename); // Ensure media dir exists (mode 0700 — only root/owner readable) await fs.mkdir(MEDIA_DIR, { recursive: true, mode: 0o700 }); const buf = Buffer.from(base64, "base64"); await fs.writeFile(filePath, buf, { mode: 0o600 }); const result = { path: filePath, mimetype, size: buf.length, messageId }; return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }], }; } catch (e) { if (e instanceof McpError) return { isError: true, content: [{ type: "text" as const, text: e.message }] }; throw e; } } ); } - src/tools/download-media.ts:11-20 (schema)Input schema for download_media: requires a 'message' object (with key.remoteJid, key.fromMe, key.id) and optional 'convertToMp4' boolean.
const schema = { message: z.object({ key: z.object({ remoteJid: z.string().min(1).describe("Chat JID"), fromMe: z.boolean().describe("Whether sent by this instance"), id: z.string().min(1).describe("Message ID"), }), }).describe("Message object containing the media"), convertToMp4: z.boolean().optional().describe("Convert audio to mp4 format (forwarded to Evolution)"), }; - src/tools/index.ts:31-31 (registration)Import of registerDownloadMedia from the download-media module.
import { registerDownloadMedia } from "./download-media.js"; - src/tools/index.ts:104-104 (registration)Call to registerDownloadMedia(server, client) in the registerAllTools function which registers the tool with the MCP server.
registerDownloadMedia(server, client); - src/util/normalize.ts:34-51 (helper)The mimeToExt helper function used by download_media to map a MIME type (e.g. 'image/jpeg') to a file extension ('jpg'), with fallback logic.
export function mimeToExt(mime: string): string { const map: Record<string, string> = { "image/jpeg": "jpg", "image/png": "png", "image/gif": "gif", "image/webp": "webp", "video/mp4": "mp4", "video/webm": "webm", "audio/ogg": "ogg", "audio/mpeg": "mp3", "audio/mp4": "m4a", "audio/wav": "wav", "application/pdf": "pdf", "application/zip": "zip", }; const sub = mime.split("/")[1]?.replace(/[^a-z0-9]/gi, ""); return map[mime] ?? (sub !== undefined && sub.length > 0 ? sub : "bin"); }