# Faxdrop Mcp

> Send real faxes from any MCP-enabled AI assistant. Wraps the FaxDrop API (PDF/DOCX/JPG/PNG, international numbers, status polling) with rate limits, dry-run mode, and audit logging.

- **Type:** MCP server
- **Install:** `agentstack add mcp-klodr-faxdrop-mcp`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [klodr](https://agentstack.voostack.com/s/klodr)
- **Installs:** 0
- **Category:** [AI & ML](https://agentstack.voostack.com/c/ai-and-ml)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [klodr](https://github.com/klodr)
- **Source:** https://github.com/klodr/faxdrop-mcp

## Install

```sh
agentstack add mcp-klodr-faxdrop-mcp
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# 📠 faxdrop-mcp

> Send real faxes from any MCP-enabled AI assistant. Wraps the [FaxDrop](https://faxdrop.com) HTTP API.

[](https://github.com/klodr/faxdrop-mcp/actions/workflows/ci.yml)
[](https://github.com/klodr/faxdrop-mcp/actions/workflows/codeql.yml)
[](https://vitest.dev)
[](https://codecov.io/gh/klodr/faxdrop-mcp)
[](https://scorecard.dev/viewer/?uri=github.com/klodr/faxdrop-mcp)
[](https://www.bestpractices.dev/projects/12578)
[](https://socket.dev/npm/package/faxdrop-mcp)
[](https://coderabbit.ai)

[](https://www.npmjs.com/package/faxdrop-mcp)
[](https://www.npmjs.com/package/faxdrop-mcp)
[](https://nodejs.org)
[](https://modelcontextprotocol.io)
[](https://modelcontextprotocol.io)
[](https://github.com/klodr/faxdrop-mcp/pulls)

[](https://github.com/sponsors/klodr)
[](https://www.patreon.com/klodr)
[](https://ko-fi.com/klodr)

A Model Context Protocol (MCP) server that lets AI assistants (Claude, Cursor, Continue, OpenClaw…) send real faxes through the [FaxDrop API](https://www.faxdrop.com/for-developers).

## ✨ 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=true` to test prompts without sending; `FAXDROP_MCP_AUDIT_LOG=/abs/path` for a JSONL trail (mode `0o600`).
- **Clean errors** — FaxDrop's 402 / 429 / 4xx surfaced as MCP `isError` with `error_type`, `hint`, `retry_after`.
- **Drop-in for any MCP client** — one `npx -y faxdrop-mcp` line in Claude Desktop / Code / Cursor / Continue / OpenClaw.
- **Verifiable releases** — Sigstore-signed + SLSA in-toto attestation + npm provenance ([verify](.github/SECURITY.md#verifying-releases)).

A ~12 KB wrapper that turns a one-week security review into a one-line config change.

## 📦 Installation

```bash
npm install -g faxdrop-mcp
```

Or use directly with `npx`:

```bash
npx faxdrop-mcp
```

## ⚙️ Configuration

The server reads `FAXDROP_API_KEY` from the environment. Get your key at [faxdrop.com/account](https://faxdrop.com/account) (Developer API → Generate Key). Keys look like `fd_live_`.

### 🤖 Claude Desktop / Claude Code

Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (or `~/.claude.json` for Claude Code):

```json
{
  "mcpServers": {
    "faxdrop": {
      "command": "npx",
      "args": ["-y", "faxdrop-mcp"],
      "env": {
        "FAXDROP_API_KEY": "fd_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
      }
    }
  }
}
```

### 🖱️ Cursor

Add to `~/.cursor/mcp.json`:

```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 false
- `coverNote` (string, ≤500) — message body
- `recipientName` (≤50), `subject` (≤200), `senderCompany` (≤100), `senderPhone` (validated E.164)

**Notifications:**

- `sendEmail` (boolean) — 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 retrievable via `faxdrop_get_fax_status`. Set to `true` to opt back in. Failed-fax emails, operator alerts, refunds and status pages are unaffected. The send response echoes `deliveryEmail: 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_limited` with `retry_after` and `X-RateLimit-*` headers.
- **Account credit balance** — `402 payment_required` when 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](https://www.faxdrop.com/for-developers) 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](.github/SECURITY.md) for the vulnerability reporting process.

## 🗺️ Roadmap

See [ROADMAP.md](docs/ROADMAP.md).

## 🌐 Ecosystem

Other MCP servers in the klodr family:

- 📧 [klodr/gmail-mcp](https://github.com/klodr/gmail-mcp) — Gmail
- 📠 [klodr/faxdrop-mcp](https://github.com/klodr/faxdrop-mcp) — Send real faxes via FaxDrop (you are here)
- 🏦 [klodr/mercury-invoicing-mcp](https://github.com/klodr/mercury-invoicing-mcp) — Mercury banking + invoicing

## 🤝 Contributing

PRs welcome. See [CONTRIBUTING.md](.github/CONTRIBUTING.md) for the test/build/lint checklist and release process.

## 📄 License

MIT — see [LICENSE](./LICENSE).

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [klodr](https://github.com/klodr)
- **Source:** [klodr/faxdrop-mcp](https://github.com/klodr/faxdrop-mcp)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-klodr-faxdrop-mcp
- Seller: https://agentstack.voostack.com/s/klodr
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
