own-domain-mcp
by nidamen
README.md
# own-domain-mcp
One command to give a family their **own-domain email** — provisioned end to end on the **MCP → API path**, idempotently, for the whole fleet.
Every family/service account is registered under that family's own domain (`service@theirdomain`, `owner@theirdomain`). For those addresses to actually receive vendor sign-up and verification mail, the domain needs Cloudflare Email Routing: a verified destination, MX records, and a forward rule. This server does all of it via the Cloudflare REST API.
## Why MCP → API (and why we keep it that way)
Direct API is the path; the browser is the last resort.
- **Deterministic & fast.** A REST call is one round trip with a known result. Driving the Cloudflare dashboard with simulated clicks (`cliclick` + screenshots) is slow, brittle, and breaks on any UI change. The day this tool was born, expanding one API token's scope had to be done by clicking dashboard dropdowns because Cloudflare won't let a token widen its own scope — that pain is exactly what we never want to repeat for a *repeatable* task.
- **Least privilege.** A scoped API token (Zone Email Routing Rules + DNS, Account Email Routing Addresses) can do everything here except first-time enable. No full logged-in session, no password, no 2FA in the loop.
- **Composable forever.** Because it's an MCP wrapping the API, the agent/fleet calls it the same way every time, and it composes into the unified `super-mcp`.
### The one honest gap
Cloudflare does **not** grant the first-time `POST /email/routing/enable` (and the `/email/routing` settings GET) to scoped API tokens. This tool:
1. tries the scoped token,
2. falls back to a **global API key** if one is configured (`CLOUDFLARE_EMAIL` + `CLOUDFLARE_GLOBAL_API_KEY`, or Keychain `cloudflare-global-api-key`) — keeping enable on the API path too,
3. otherwise reports `enable: blocked` with guidance and **still** sets the catch-all/rules, which activate the instant routing is enabled (one dashboard click, once per zone).
Everything else — destinations, catch-all, rules, status — is pure scoped-token API.
## Tools
| Tool | What it does |
|------|--------------|
| `own_domain_targets` | List eligible family domains (all zones minus the exclude policy) + the forward-to inbox. Read-only. |
| `own_domain_status` | Per-domain (or fleet-wide) routing state: enabled, destination verified, catch-all target, functional. Read-only. |
| `own_domain_destination_ensure` | Ensure the forward-to inbox is a verified account destination (creates + triggers verification). `confirm`. |
| `own_domain_provision` | Full idempotent setup for ONE domain: destination → enable → catch-all/addresses → status. `confirm`. |
| `own_domain_fanout` | The same for EVERY eligible family domain — the one fleet command. `confirm`. |
## Policy / safety
`config/own-domains.json` (env-overridable):
- `forwardTo` — the inbox every address forwards to (e.g. `you@example.com`).
- `mode` — `catch_all` (forward every address) or `addresses` (only `owner@`, `service@`).
- `exclude` — zones **never** touched. Empty by default; add any zone you never want catch-all'd to a personal inbox (e.g. a medical/PHI domain, or shared infrastructure).
- `include` — optional allowlist; when empty, all zones except `exclude` are eligible.
## Config
```bash
npm install && npm run build
claude mcp add own-domain -- node /path/to/own-domain-mcp/dist/index.js
```
Credentials (env first, then macOS Keychain):
- Scoped token: `OWN_DOMAIN_CF_TOKEN` / `CLOUDFLARE_API_TOKEN`, or Keychain `cloudflare-api-token` / `cloudflare-email-routing-token` (account from `CLOUDFLARE_KEYCHAIN_ACCOUNT` or `$USER`).
- Optional global key (enable only): `CLOUDFLARE_EMAIL` + `CLOUDFLARE_GLOBAL_API_KEY`, or Keychain `cloudflare-global-api-key`.
Env overrides: `OWN_DOMAIN_FORWARD_TO`, `OWN_DOMAIN_MODE`, `OWN_DOMAIN_CONFIG`, `OWN_DOMAIN_ACCOUNT_ID`.
```bash
npm test # unit tests (mock fetch, no live API)
```
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessSyncing