About Faxdrop Mcp
Faxdrop Mcp wraps the FaxDrop API as an MCP server for sending and tracking faxes from compatible AI clients. It adds number approval, file isolation, dry-run testing, and audit logging.
Key features
- 01Send PDF, DOCX, JPEG, and PNG faxes
- 02Check fax delivery status
- 03Pair numbers and restrict type and country
- 04Restrict outbox paths and validate files
- 05Dry runs and redacted audit logs
Use cases
- 01Send documents by fax from an AI workflow
- 02Approve a new recipient number before sending
- 03Test a send request without transmitting a fax
MCP support
The MCP server exposes tools to send faxes, pair recipient numbers, and check delivery status.
Configuration
Local server config is the same for every client. Copy it into your client’s MCP config file.
Tools
3Saved tool list · updated 2w ago
| Parameter | Type | Required | Description |
|---|---|---|---|
| subject | string | No | Cover page subject / RE: line (max 200 chars). |
| filePath | string | Yes | Absolute path to the document to fax (PDF, DOCX, JPEG, or PNG, ≤10MB). |
| coverNote | string | No | Message printed on the cover page (max 500 chars). Only used when includeCover is true. |
| sendEmail | boolean | No | Whether FaxDrop should email the sender a per-fax delivery confirmation. Defaults to **false** (suppress) — the MCP is built for batch / agent workflows where one email per fax floods the inbox, and the same information is already available via `faxdrop_get_fax_status`. Set to true to opt back into the confirmation email. Failed-fax emails, operator alerts, refunds and status pages are unaffected. The send response echoes `deliveryEmail: enabled | suppressed`. |
| senderName | string | Yes | Sender display name shown on the cover page. |
| senderEmail | string | Yes | Sender email for delivery confirmation. |
| senderPhone | string | No | Sender callback number shown on the cover page (E.164 format). |
| includeCover | boolean | 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. |
| recipientName | string | No | Recipient display name on the cover page, e.g. "Dr. Jane Smith" (max 50 chars). |
| senderCompany | string | No | Sender company shown on the cover page (max 100 chars). |
| recipientNumber | string | Yes | Recipient fax number, international (E.164) format with leading + and country code, e.g. +12125551234 |
Details
From the project README📠 faxdrop-mcp
Send real faxes from any MCP-enabled AI assistant. Wraps the FaxDrop HTTP API.
A Model Context Protocol (MCP) server that lets AI assistants (Claude, Cursor, Continue, OpenClaw…) send real faxes through the FaxDrop API.
✨ Why this MCP?
Faxing is still required by US healthcare, government forms, and a long tail of legal/financial workflows. FaxDrop is a hosted fax service with a clean HTTP API and a free tier (2 faxes/month). This MCP exposes it to LLMs with the safeguards an agent platform actually needs.
🤔 Why not just call the FaxDrop API directly?
You can. But every agent that does ends up re-implementing the same handful of guards. This MCP gives them to you for free:
- Input validation — absolute-path + extension + 10 MB cap on the upload (all before the file is opened); E.164 regex on the fax number; no SSRF, no path traversal.
- TOCTOU-safe read — file descriptor pinned with
fs.open(), size enforced continuously while reading. - No secret leakage — error objects strip the response body; the audit log keeps only an explicit allowlist of FaxDrop response-shape fields (
recipientNumber,faxId,id) in clear, blocks the credential set (apiKey/authorization/password/ …), and elides every other field with a length marker ([ELIDED:NNN]). Property-tested with fast-check. - Dry-run + audit log —
FAXDROP_MCP_DRY_RUN=trueto test prompts without sending;FAXDROP_MCP_AUDIT_LOG=/abs/pathfor a JSONL trail (mode0o600). - Clean errors — FaxDrop's 402 / 429 / 4xx surfaced as MCP
isErrorwitherror_type,hint,retry_after. - Drop-in for any MCP client — one
npx -y faxdrop-mcpline in Claude Desktop / Code / Cursor / Continue / OpenClaw. - Verifiable releases — Sigstore-signed + SLSA in-toto attestation + npm provenance (verify).
A ~12 KB wrapper that turns a one-week security review into a one-line config change.
📦 Installation
npm install -g faxdrop-mcp
Or use directly with npx:
npx faxdrop-mcp
⚙️ Configuration
The server reads FAXDROP_API_KEY from the environment. Get your key at faxdrop.com/account (Developer API → Generate Key). Keys look like fd_live_<32 hex>.
🤖 Claude Desktop / Claude Code
Add to ~/Library/Application Support/Claude/claude_desktop_config.json (or ~/.claude.json for Claude Code):
{
"mcpServers": {
"faxdrop": {
"command": "npx",
"args": ["-y", "faxdrop-mcp"],
"env": {
"FAXDROP_API_KEY": "fd_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
}
}
}
🖱️ Cursor
Add to ~/.cursor/mcp.json:
{
"mcpServers": {
"faxdrop": {
"command": "npx",
"args": ["-y", "faxdrop-mcp"],
"env": {
"FAXDROP_API_KEY": "fd_live_..."
}
}
}
}
🦀 OpenClaw
Add to ~/.openclaw/openclaw.json, then restart the gateway (docker restart openclaw-openclaw-gateway-1 or your equivalent).
🛠️ Tools (3)
📤 faxdrop_send_fax
Send a fax. Uploads a local document from the outbox (default ~/FaxOutbox/) to a fax number in international (E.164) format.
Required:
filePath(string, absolute) — PDF, DOCX, JPEG, or PNG, ≤10 MB. Must live inside the outbox.recipientNumber(string) — E.164, e.g.+12125551234. Subject to the 3-layer phone gate (TYPE → COUNTRY → per-number).senderName(string)senderEmail(string)
Optional cover-page fields (printed only when includeCover is true):
includeCover(boolean) — free accounts always include a branded cover; paid accounts default to falsecoverNote(string, ≤500) — message bodyrecipientName(≤50),subject(≤200),senderCompany(≤100),senderPhone(validated E.164)
Notifications:
sendEmail(boolean) — whether FaxDrop should email the sender a per-fax delivery confirmation. Defaults tofalse(suppress) — the MCP is built for batch/agent workflows where one email per fax floods the inbox, and the same information is already retrievable viafaxdrop_get_fax_status. Set totrueto opt back in. Failed-fax emails, operator alerts, refunds and status pages are unaffected. The send response echoesdeliveryEmail: enabled | suppressed.
Returns: { success, faxId, status, statusUrl, deliveryEmail }
🔗 faxdrop_pair_number
Add a fax number to the paired whitelist (~/.faxdrop-mcp/paired.json). Only effective when FAXDROP_MCP_NUMBER_GATE=pairing (default). The number must still pass the TYPE and COUNTRY checks (no bypass). Always confirm with the user before pairing — paired numbers can be faxed without further per-number approval.
Required:
recipientNumber(string) — E.164
Returns: { paired, country, type }
📊 faxdrop_get_fax_status
Check the delivery status of a previously sent fax. Terminal statuses (delivered / failed / partial) are cached process-wide (LRU 100 entries, whitelist-sliced) — re-polling a finished fax short-circuits with a _cached: true marker to spare your FaxDrop quota.
Recommended polling cadence: every ~5s for the first 2 min, then every ~30s for up to 10 min, stop on terminal status.
Required:
faxId(string)
Returns: { id, status, recipientNumber?, pages?, completedAt?, _cached? }
🛡️ Safeguards
| Knob | Env var | Default | Notes |
|---|---|---|---|
| Outbox jail | FAXDROP_MCP_WORK_DIR=/abs/path | ~/FaxOutbox/ (auto-created mode 0o700) | Every filePath must live inside this directory after realpath canonicalization. Symlinks to outside the outbox are rejected. |
| Number gate | FAXDROP_MCP_NUMBER_GATE=open|pairing|closed | pairing | pairing requires HITL approval via faxdrop_pair_number before a new number can be faxed. closed disables runtime pairing (paired.json edited out-of-band). |
| Allowed types | FAXDROP_MCP_ALLOWED_TYPES=... | FIXED_LINE,FIXED_LINE_OR_MOBILE,VOIP,TOLL_FREE | libphonenumber NumberType allow-list. |
| Allowed countries | FAXDROP_MCP_ALLOWED_COUNTRIES=... | US,CA,PR,GU,VI,AS,MP | ISO-3166-1 alpha-2 allow-list (US/CA + US territories). |
| State directory | FAXDROP_MCP_STATE_DIR=/abs/path | ~/.faxdrop-mcp/ (mode 0o700) | Where paired.json lives (mode 0o600, atomic write). |
| Dry run | FAXDROP_MCP_DRY_RUN=true | off | Write tools (faxdrop_send_fax, faxdrop_pair_number) return the would-be payload (passed through the same allowlist redaction as the audit log — see row below) and never call FaxDrop or touch paired.json. |
| Audit log | FAXDROP_MCP_AUDIT_LOG=/abs/path/audit.log | off | Append-only JSON Lines (file mode 0o600). Allowlist-based redaction: only FaxDrop response-shape fields (recipientNumber, faxId, id) are kept in clear; known credential keys (apiKey / authorization / password / secret / token) are replaced with [REDACTED]; every other field is elided with a [ELIDED:NNN] length marker. This is a fail-closed design: a new API field added upstream is hidden by default, not leaked. |
⚠️ Error catalog
Every failure is returned as isError: true with a structured error_type, message, and (when applicable) hint and retry_after. Programmatic consumers can match on error_type (in structuredContent) to drive retry logic.
error_type | Layer | Trigger | Suggested action |
|---|---|---|---|
phone_parse | input | Recipient number can't be parsed by libphonenumber. | Ask user for an E.164 number. |
phone_type | policy | Phone type (e.g. MOBILE) not in FAXDROP_MCP_ALLOWED_TYPES. | Use a fax line, or extend the env var. |
phone_country | policy | Country not in FAXDROP_MCP_ALLOWED_COUNTRIES. | Confirm with the user; extend the env var if intentional. |
phone_gate | policy | Number not in paired.json and gate is pairing or closed. | In pairing mode: call faxdrop_pair_number first. In closed: edit paired.json out-of-band. |
pair_disabled | policy | faxdrop_pair_number called outside pairing mode. | Set FAXDROP_MCP_NUMBER_GATE=pairing. |
bad_request | filesystem | Path is relative, outside outbox, leaf-symlink, missing, oversized, or has an unsupported extension. | The accompanying hint describes the exact remedy. |
unauthorized | upstream | FaxDrop returned 401. | Check FAXDROP_API_KEY in your MCP client config. |
payment_required | upstream | FaxDrop returned 402 (out of credits). | Top up at the FaxDrop pricing page. |
rate_limited | upstream | FaxDrop returned 429. | Wait retry_after seconds; the hint shows the bucket that was hit. |
invalid_response | upstream | FaxDrop returned a non-JSON body (proxy interception, incident page). | Body is discarded for safety; check FaxDrop status page. |
fax_error | upstream (fallback) | FaxDrop returned an error with no error_type field. | Read the message; treat as transient. |
🚦 Rate limits & quotas
Two independent caps gate every fax send, both enforced by FaxDrop:
- Per-key rate limits (per-minute / per-hour / per-day buckets) —
429 rate_limitedwithretry_afterandX-RateLimit-*headers. - Account credit balance —
402 payment_requiredwhen you run out, with a top-up hint.
The MCP does not add its own limiter; it forwards FaxDrop's response as a clean isError: true with error_type, hint, and retry_after. See FaxDrop's API docs for the current numbers.
🔒 Security
- Always confirm with the user (recipient, file, cover-page) before invoking
faxdrop_send_fax. This is also baked into the tool description. - The MCP reads files from the user's local filesystem — only expose this server to agents you trust.
- Test prompts safely with
FAXDROP_MCP_DRY_RUN=true. - Behind a local proxy that answers DNS itself (Surge in enhanced mode hands out
198.18.0.0/15), setMCP_TRUSTED_CIDRS=198.18.0.0/15so the SSRF guard accepts its addresses; prefer a single IP whenever the proxy allows it. Refused at startup: a malformed entry, more than 131,072 addresses in all (one range: IPv4/15at most, IPv6/111), anything that touches loopback, link-local,0.0.0.0/8or an IPv6 range carrying an IPv4 (an IPv4-mapped address such as::ffff:10.47.83.1counts as its IPv4). - See SECURITY.md for the vulnerability reporting process.
🗺️ Roadmap
See ROADMAP.md.
🌐 Ecosystem
Other MCP servers in the klodr family:
- 📧 klodr/gmail-mcp — Gmail
- 📠 klodr/faxdrop-mcp — Send real faxes via FaxDrop (you are here)
- 🏦 klodr/mercury-invoicing-mcp — Mercury banking + invoicing
🤝 Contributing
PRs welcome. See CONTRIBUTING.md for the test/build/lint checklist and release process.
📄 License
MIT — see LICENSE.
Comments
New to MCP?
From what the protocol is to installing your first server, these answers cover most first questions.
An open protocol that defines how AI applications connect to external tools and data in a uniform way. Clients talk to the model; MCP servers provide tools, resources and prompt templates. They exchange standard messages, so one server can be reused by many clients.