Skip to main content
Glama
klodr

faxdrop-mcp

faxdrop_send_fax

Destructive

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

TableJSON Schema
NameRequiredDescriptionDefault
filePathYesAbsolute path to the document to fax (PDF, DOCX, JPEG, or PNG, ≤10MB).
recipientNumberYesRecipient fax number, international (E.164) format with leading + and country code, e.g. +12125551234
senderNameYesSender display name shown on the cover page.
senderEmailYesSender email for delivery confirmation.
includeCoverNoInclude 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.
coverNoteNoMessage printed on the cover page (max 500 chars). Only used when includeCover is true.
recipientNameNoRecipient display name on the cover page, e.g. "Dr. Jane Smith" (max 50 chars).
subjectNoCover page subject / RE: line (max 200 chars).
senderCompanyNoSender company shown on the cover page (max 100 chars).
senderPhoneNoSender callback number shown on the cover page (E.164 format).

Implementation Reference

  • 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 },
    );
  • 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(),
    },
  • 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 },
    );
  • 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);
    }
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations have destructiveHint:true; description adds side effects: charges balance, creates audit log, allocates fax ID. Also explains file location constraint. No contradictions.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Well-structured with labeled sections (USE WHEN, DO NOT USE, SIDE EFFECTS, FILE LOCATION, RETURNS). Somewhat verbose but each sentence serves a purpose; front-loaded with core function.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Covers all critical aspects: purpose, usage guidelines, side effects, file location, return value structure, and even instructions to confirm with user. Comprehensive for a complex tool with 10 parameters and no output schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, baseline 3. Description adds context: file must be in outbox, includeCover behavior differs for free vs paid accounts, cover page fields conditional. Adds meaningful value beyond schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Clearly states 'Send a real fax via FaxDrop'. Verb and resource are explicit, and it distinguishes from sibling tools (faxdrop_get_fax_status for polling, faxdrop_pair_number for pairing).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit 'USE WHEN' and 'DO NOT USE' sections with specific examples (medical records, legal forms) and restrictions (digital delivery, files outside outbox, non-fax numbers). Also mentions 3-layer phone gate.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/klodr/faxdrop-mcp'

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