faxdrop-mcp
The faxdrop-mcp server enables AI assistants to send and manage real faxes through the FaxDrop API, with built-in safeguards and controlled access.
Send a fax (
faxdrop_send_fax): Upload a local document (PDF, DOCX, JPEG, or PNG, β€10 MB) from a designated outbox directory to a recipient fax number in E.164 format. RequiresfilePath,recipientNumber,senderName, andsenderEmail. Optional cover page fields includesubject,coverNote,recipientName,senderCompany, andsenderPhone. Returns afaxIdand initial delivery status.Pair/whitelist a recipient number (
faxdrop_pair_number): Pre-approve a fax number so future sends skip per-number confirmation. Only available when the server is inpairinggate mode. Still enforces phone type and country validation.Check fax delivery status (
faxdrop_get_fax_status): Poll the delivery status of a sent fax byfaxId. Returns statuses such asqueued,sending,delivered,failed, orpartial. Terminal statuses are cached to avoid redundant API calls.
Key safeguards include: file path validation (outbox jail, no path traversal, 10 MB cap), E.164 phone number validation with type/country allow-lists, dry-run mode (FAXDROP_MCP_DRY_RUN=true) for safe testing, an append-only audit log with credential redaction, and structured error responses with actionable hints.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@faxdrop-mcpsend a fax to +12125551234 with the contract.pdf file"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
π 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.
Related MCP server: PDFGate MCP Server
π¦ Installation
npm install -g faxdrop-mcpOr 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 |
|
| Every |
Number gate |
|
|
|
Allowed types |
|
| libphonenumber |
Allowed countries |
|
| ISO-3166-1 alpha-2 allow-list (US/CA + US territories). |
State directory |
|
| Where |
Dry run |
| off | Write tools ( |
Audit log |
| off | Append-only JSON Lines (file mode |
β οΈ 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.
| Layer | Trigger | Suggested action |
| input | Recipient number can't be parsed by libphonenumber. | Ask user for an E.164 number. |
| policy | Phone type (e.g. MOBILE) not in | Use a fax line, or extend the env var. |
| policy | Country not in | Confirm with the user; extend the env var if intentional. |
| policy | Number not in | In |
| policy |
| Set |
| filesystem | Path is relative, outside outbox, leaf-symlink, missing, oversized, or has an unsupported extension. | The accompanying |
| upstream | FaxDrop returned 401. | Check |
| upstream | FaxDrop returned 402 (out of credits). | Top up at the FaxDrop pricing page. |
| upstream | FaxDrop returned 429. | Wait |
| upstream | FaxDrop returned a non-JSON body (proxy interception, incident page). | Body is discarded for safety; check FaxDrop status page. |
| upstream (fallback) | FaxDrop returned an error with no | 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.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.
Available Tools
3 toolsfaxdrop_get_fax_statusARead-only
Check the delivery status of a previously sent fax.
USE WHEN: polling for the outcome of a fax sent via faxdrop_send_fax. Status values: queued | sending | completed (or legacy delivered) | failed | partial.
DO NOT USE: for faxes sent outside this MCP (no provenance β server returns 404). Once status is completed (or delivered), failed, or partial, STOP polling β these are terminal.
SIDE EFFECTS: each non-cached call hits the FaxDrop API and counts toward its per-key rate limits (no monetary cost). Terminal results are cached process-wide.
POLLING STRATEGY: every ~5s for the first 2 min, then every ~30s up to 10 min. Most US faxes complete in <90s.
RETURNS: provider status object + optional _cached: true flag.
| Name | Required | Description | Default |
|---|---|---|---|
| faxId | Yes | The fax ID returned by faxdrop_send_fax (e.g. fax_abc123). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Discloses side effects (API hit, rate limits, caching), terminal statuses, and aligns with annotations (readOnlyHint, openWorldHint). No contradiction.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Concise yet thorough, with clear sections (USE WHEN, DO NOT USE, SIDE EFFECTS, POLLING STRATEGY). No wasted words.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Without output schema, description explains return values (status object + _cached flag). Covers all necessary context for a polling tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema covers 100% of params; description adds context that faxId comes from faxdrop_send_fax, aiding correct usage.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states 'Check the delivery status of a previously sent fax.' It distinguishes from siblings by specifying it polls for faxes sent via faxdrop_send_fax.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly provides when to use (polling for fax sent via this MCP), when not to use (faxes sent outside), and includes a detailed polling strategy.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
faxdrop_pair_numberAIdempotent
Add a fax number to the per-recipient whitelist (paired.json).
USE WHEN: server runs with FAXDROP_MCP_NUMBER_GATE=pairing and the user wants to pre-approve a recurring recipient (clinic, lawyer, supplier) so future faxdrop_send_fax calls skip the per-number confirmation step.
DO NOT USE: when gate mode is open (no whitelist needed) or closed (whitelist edited out-of-band only β pairing rejected). For one-off faxes, skip pairing and call faxdrop_send_fax directly.
SIDE EFFECTS: writes to ~/.faxdrop-mcp/paired.json (or $FAXDROP_MCP_STATE_DIR/paired.json). Persistent across runs. ALWAYS confirm with the user β paired numbers can be faxed without further per-number approval.
VALIDATION: TYPE + COUNTRY checks still apply (no bypass). Mobile/landline/premium numbers are rejected even at pairing time.
RETURNS: { paired, country, type }.
| Name | Required | Description | Default |
|---|---|---|---|
| recipientNumber | Yes | Recipient fax number, international (E.164) format with leading + and country code, e.g. +12125551234 |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations (destructiveHint=false, idempotentHint=true, openWorldHint=true) are consistent. The description adds valuable behavioral context: it writes to a persistent file, requires user confirmation, and notes that validation still applies. 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.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is structured with clear sections (USE WHEN, DO NOT USE, SIDE EFFECTS, VALIDATION, RETURNS). Every sentence adds value, and the total length is appropriate for the complexity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Despite no output schema, the description explicitly states the return format ({ paired, country, type }). It covers side effects, persistence, user confirmation, and validation. Annotations and one parameter make this a well-rounded description.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema has 100% coverage for the single parameter (recipientNumber), describing E.164 format. The description adds meaning by explaining that validation checks (type, country) still apply at pairing time, which goes beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's function: 'Add a fax number to the per-recipient whitelist (paired.json).' It distinguishes itself from siblings (faxdrop_get_fax_status and faxdrop_send_fax) by specifying that it is for pre-approving recurring recipients to skip confirmation in faxdrop_send_fax.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly gives when to use ('server runs with FAXDROP_MCP_NUMBER_GATE=pairing and the user wants to pre-approve a recurring recipient'), when not to use ('gate mode is open or closed'), and an alternative ('For one-off faxes, skip pairing and call faxdrop_send_fax directly').
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
faxdrop_send_faxADestructive
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.
| Name | Required | Description | Default |
|---|---|---|---|
| subject | No | Cover page subject / RE: line (max 200 chars). | |
| filePath | Yes | Absolute path to the document to fax (PDF, DOCX, JPEG, or PNG, β€10MB). | |
| coverNote | No | Message printed on the cover page (max 500 chars). Only used when includeCover is true. | |
| sendEmail | 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 | Yes | Sender display name shown on the cover page. | |
| senderEmail | Yes | Sender email for delivery confirmation. | |
| senderPhone | No | Sender callback number shown on the cover page (E.164 format). | |
| 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. | |
| recipientName | No | Recipient display name on the cover page, e.g. "Dr. Jane Smith" (max 50 chars). | |
| senderCompany | No | Sender company shown on the cover page (max 100 chars). | |
| recipientNumber | Yes | Recipient fax number, international (E.164) format with leading + and country code, e.g. +12125551234 |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare destructiveHint=true and openWorldHint=true, and the description adds substantial value beyond them: balance charges, free-tier branded-cover behavior, audit-log creation, server-side fax ID allocation, the outbox file-location constraint, and an explicit 'ALWAYS confirm recipient + file + cover' instruction. This is exactly the mutation/irreversibility context an agent needs.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Header-structured and front-loaded with purpose, then use/avoid, side effects, file rules, and return shape. Dense but every block carries operational weight; only the free-tier cover detail borders on surplus.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, yet the description supplies the return shape ({ faxId, status: 'queued', ... }), the polling next step, and the preconditions (outbox location, balance charge). Complete for an 11-param mutation tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so baseline is 3, but the description adds meaning the schema lacks: the file must reside inside the outbox (rejected otherwise, with the FAXDROP_MCP_WORK_DIR override), and the cover-page fields are gated on includeCover β cross-parameter behavior not captured in the flat schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource ('Send a real fax via FaxDrop') and immediately differentiates from the sibling by pointing to faxdrop_get_fax_status for polling. An agent can tell exactly what this does versus the read-status tool.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit USE WHEN section lists document types and use cases, DO NOT USE section names the excluded channels (email, sftp) and the 3-layer phone gate that rejects mobile/landline/premium. Alternatives and conditions are all spelled out.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
1 tool update
v0.10.1- Changed
faxdrop_send_fax1 field changed- changed
Input schema / properties / senderEmail / patternPrevious value: -"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"New value: +"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
1 tool update
v0.10.0- Changed
faxdrop_send_fax1 field changed- added
Input schema / properties / sendEmailAdded value: +{ + "default": false, + "description": "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`.", + "type": "boolean" +}
3 tool updates
v0.8.7- Added
faxdrop_get_fax_status - Added
faxdrop_pair_number - Added
faxdrop_send_fax
3 tool updates
v0.8.5- Removed
faxdrop_get_fax_status - Removed
faxdrop_pair_number - Removed
faxdrop_send_fax
3 tool updates
v0.8.4- Added
faxdrop_get_fax_status - Added
faxdrop_pair_number - Added
faxdrop_send_fax
3 tool updates
v0.8.2- Removed
faxdrop_get_fax_status - Removed
faxdrop_pair_number - Removed
faxdrop_send_fax
3 tool updates
- First observed
faxdrop_get_fax_status - First observed
faxdrop_pair_number - First observed
faxdrop_send_fax
TDQS
Scored across 3 tools
The three tools target clearly distinct actions: sending (faxdrop_send_fax), checking outcome (faxdrop_get_fax_status), and pre-approving a recipient (faxdrop_pair_number). Descriptions explicitly cross-reference each other and warn when *not* to use a tool, leaving no realistic misselection path.
All names use the same faxdrop_<verb>_<object> snake_case pattern (send_fax, get_fax_status, pair_number). No mixed conventions or vague verbs.
Three tools is exactly the surface needed for a narrow fax-sending domain: send, poll, and whitelist. Each earns its place and there is no padding.
Core lifecycle (send β poll to terminal state) plus recipient pre-approval is covered. Minor gaps exist in adjacent operations such as cancelling a queued fax, listing sent history, or checking account balance, but agents can work around these.
Maintenance
Related MCP Connectors
Send, receive, and verify faxes from AI assistants and agents via FaxSeal.
Send real faxes from AI assistants. Pay per fax ($1 + $0.25/page), no account or API key.
Send free or paid faxes to US/Canada from Claude, ChatGPT, and other AI agents.
- Fax.PlusOAuthplus.fax.app
Send and receive faxes from any MCP-compatible AI assistant using the Fax.Plus API
Related MCP Servers
- AlicenseAqualityAmaintenanceMercury Banking MCP server with full Invoicing API support. Read accounts/transactions and create/manage recurring invoices via Model Context Protocol.3643 npm4MIT

PDFGate MCP Serverofficial
AlicenseAqualityAmaintenanceEnables AI assistants to generate PDFs, manage documents, and handle e-signatures via the PDFGate API.24181 npmMIT- AlicenseNot gradedqualityDmaintenanceEnables AI agents to send free and paid faxes to US and Canada numbers, check fax status, and purchase prepaid credits, all without leaving the chat.MIT
- AlicenseNot gradedqualityDmaintenanceSend, receive, and verify faxes from AI assistants and agents via the Model Context Protocol.33 npmMIT