faxdrop_send_fax
Send a real fax to any fax number by uploading a document (PDF, DOCX, JPEG, PNG ≤10MB). Specify recipient number, sender info, and optional cover page. Returns a fax ID for tracking.
Instructions
Send a real fax via FaxDrop.
USE WHEN: user needs to fax a document (PDF, DOCX, JPEG, PNG ≤10MB) to a fax number — medical records, legal forms, government submissions, recipients who only accept fax.
DO NOT USE: for digital delivery (email, sftp), for files outside the outbox, for non-fax numbers — the 3-layer phone gate (TYPE → COUNTRY → per-number policy) rejects mobile/landline/premium.
SIDE EFFECTS: charges FaxDrop balance (or consumes free credits + adds branded cover on free tier), creates an audit log entry, allocates a fax ID server-side. ALWAYS confirm recipient + file + cover with the user before calling.
FILE LOCATION: document must live inside the outbox (default ~/FaxOutbox/, override via FAXDROP_MCP_WORK_DIR). Files outside are rejected — ask the user to copy in first.
RETURNS: { faxId, status: "queued", ... } — poll with faxdrop_get_fax_status.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| filePath | Yes | Absolute path to the document to fax (PDF, DOCX, JPEG, or PNG, ≤10MB). | |
| recipientNumber | Yes | Recipient fax number, international (E.164) format with leading + and country code, e.g. +12125551234 | |
| senderName | Yes | Sender display name shown on the cover page. | |
| senderEmail | Yes | Sender email for delivery confirmation. | |
| includeCover | No | Include a FaxDrop cover page. Free accounts always include a branded cover regardless; paid accounts default to false. The cover-page fields below (coverNote, recipientName, subject, senderCompany, senderPhone) are only printed when includeCover is true. | |
| coverNote | No | Message printed on the cover page (max 500 chars). Only used when includeCover is true. | |
| recipientName | No | Recipient display name on the cover page, e.g. "Dr. Jane Smith" (max 50 chars). | |
| subject | No | Cover page subject / RE: line (max 200 chars). | |
| senderCompany | No | Sender company shown on the cover page (max 100 chars). | |
| senderPhone | No | Sender callback number shown on the cover page (E.164 format). |
Implementation Reference
- src/tools/fax.ts:90-131 (handler)Handler function for faxdrop_send_fax: validates the recipient number via the 3-layer phone gate, reads the file from the outbox jail, then sends it via FaxDropClient.sendFax().
async (args) => { // 3-layer phone-number gate: TYPE → COUNTRY → per-number policy // (open / pairing / closed). All three must pass before the fax is // dispatched. See src/phone-gate.ts for semantics + env overrides. const gate = validateAll(args.recipientNumber); if (!gate.ok) { return errorResult({ error_type: `phone_${gate.layer}`, message: gate.reason, hint: gate.hint, }); } // Outbox jail + symlink hardening + extension allow-list + size cap + // chunked TOCTOU-safe read. See src/file-io.ts for the threat model. let opened; try { opened = await openInsideOutbox(args.filePath); } catch (err) { if (err instanceof FileIoError) { return errorResult({ error_type: "bad_request", message: err.message, hint: err.hint, }); } /* v8 ignore next -- only reached if openInsideOutbox throws something other than a FileIoError (kernel-level fs error, OOM, etc.); the normal paths route through FileIoError. Re-throw rather than mask. */ throw err; } const { filePath: _filePath, ...rest } = args; void _filePath; const data = await client.sendFax({ ...rest, fileBytes: opened.bytes, filename: opened.filename, mimeType: opened.mimeType, }); return textResult(data); }, { title: "Send Fax", destructiveHint: true }, ); - src/tools/fax.ts:46-89 (schema)Zod input schema for faxdrop_send_fax: defines all parameters including filePath, recipientNumber, senderName, senderEmail, and optional cover-page fields.
filePath: z .string() .min(1) .describe("Absolute path to the document to fax (PDF, DOCX, JPEG, or PNG, ≤10MB)."), recipientNumber: FAX_NUMBER, senderName: z .string() .min(1) .max(100) .describe("Sender display name shown on the cover page."), senderEmail: EMAIL, includeCover: z .boolean() .optional() .describe( "Include a FaxDrop cover page. Free accounts always include a branded cover regardless; paid accounts default to false. The cover-page fields below (coverNote, recipientName, subject, senderCompany, senderPhone) are only printed when includeCover is true.", ), coverNote: z .string() .max(500) .optional() .describe( "Message printed on the cover page (max 500 chars). Only used when includeCover is true.", ), recipientName: z .string() .max(50) .optional() .describe( 'Recipient display name on the cover page, e.g. "Dr. Jane Smith" (max 50 chars).', ), subject: z .string() .max(200) .optional() .describe("Cover page subject / RE: line (max 200 chars)."), senderCompany: z .string() .max(100) .optional() .describe("Sender company shown on the cover page (max 100 chars)."), senderPhone: SENDER_PHONE.optional(), }, - src/tools/fax.ts:29-131 (registration)Tool registration via defineTool() inside registerFaxTools(): binds the name 'faxdrop_send_fax' to its schema, description, handler, and annotations.
defineTool( server, "faxdrop_send_fax", [ "Send a real fax via FaxDrop.", "", "USE WHEN: user needs to fax a document (PDF, DOCX, JPEG, PNG ≤10MB) to a fax number — medical records, legal forms, government submissions, recipients who only accept fax.", "", "DO NOT USE: for digital delivery (email, sftp), for files outside the outbox, for non-fax numbers — the 3-layer phone gate (TYPE → COUNTRY → per-number policy) rejects mobile/landline/premium.", "", "SIDE EFFECTS: charges FaxDrop balance (or consumes free credits + adds branded cover on free tier), creates an audit log entry, allocates a fax ID server-side. ALWAYS confirm recipient + file + cover with the user before calling.", "", "FILE LOCATION: document must live inside the outbox (default `~/FaxOutbox/`, override via FAXDROP_MCP_WORK_DIR). Files outside are rejected — ask the user to copy in first.", "", 'RETURNS: `{ faxId, status: "queued", ... }` — poll with `faxdrop_get_fax_status`.', ].join("\n"), { filePath: z .string() .min(1) .describe("Absolute path to the document to fax (PDF, DOCX, JPEG, or PNG, ≤10MB)."), recipientNumber: FAX_NUMBER, senderName: z .string() .min(1) .max(100) .describe("Sender display name shown on the cover page."), senderEmail: EMAIL, includeCover: z .boolean() .optional() .describe( "Include a FaxDrop cover page. Free accounts always include a branded cover regardless; paid accounts default to false. The cover-page fields below (coverNote, recipientName, subject, senderCompany, senderPhone) are only printed when includeCover is true.", ), coverNote: z .string() .max(500) .optional() .describe( "Message printed on the cover page (max 500 chars). Only used when includeCover is true.", ), recipientName: z .string() .max(50) .optional() .describe( 'Recipient display name on the cover page, e.g. "Dr. Jane Smith" (max 50 chars).', ), subject: z .string() .max(200) .optional() .describe("Cover page subject / RE: line (max 200 chars)."), senderCompany: z .string() .max(100) .optional() .describe("Sender company shown on the cover page (max 100 chars)."), senderPhone: SENDER_PHONE.optional(), }, async (args) => { // 3-layer phone-number gate: TYPE → COUNTRY → per-number policy // (open / pairing / closed). All three must pass before the fax is // dispatched. See src/phone-gate.ts for semantics + env overrides. const gate = validateAll(args.recipientNumber); if (!gate.ok) { return errorResult({ error_type: `phone_${gate.layer}`, message: gate.reason, hint: gate.hint, }); } // Outbox jail + symlink hardening + extension allow-list + size cap + // chunked TOCTOU-safe read. See src/file-io.ts for the threat model. let opened; try { opened = await openInsideOutbox(args.filePath); } catch (err) { if (err instanceof FileIoError) { return errorResult({ error_type: "bad_request", message: err.message, hint: err.hint, }); } /* v8 ignore next -- only reached if openInsideOutbox throws something other than a FileIoError (kernel-level fs error, OOM, etc.); the normal paths route through FileIoError. Re-throw rather than mask. */ throw err; } const { filePath: _filePath, ...rest } = args; void _filePath; const data = await client.sendFax({ ...rest, fileBytes: opened.bytes, filename: opened.filename, mimeType: opened.mimeType, }); return textResult(data); }, { title: "Send Fax", destructiveHint: true }, ); - src/client.ts:81-97 (helper)FaxDropClient.sendFax(): constructs the multipart FormData payload and POSTs it to /api/send-fax. Called by the handler with already-opened file bytes.
async sendFax(args: SendFaxArgs): Promise<unknown> { const blob = new Blob([new Uint8Array(args.fileBytes)], { type: args.mimeType }); const form = new FormData(); form.set("file", blob, args.filename); form.set("recipientNumber", args.recipientNumber); form.set("senderName", args.senderName); form.set("senderEmail", args.senderEmail); if (args.includeCover !== undefined) form.set("includeCover", String(args.includeCover)); if (args.coverNote !== undefined) form.set("coverNote", args.coverNote); if (args.recipientName !== undefined) form.set("recipientName", args.recipientName); if (args.subject !== undefined) form.set("subject", args.subject); if (args.senderCompany !== undefined) form.set("senderCompany", args.senderCompany); if (args.senderPhone !== undefined) form.set("senderPhone", args.senderPhone); return this.requestRaw("POST", "/api/send-fax", form); }