sabit-mcp
by ruwadgroup
README.md
<div align="center">
# Sabit · ثابت
**Verified, not guessed.**
A self-hostable **email-verification engine** in Rust. A layered, free-before-expensive pipeline (syntax → IDN/EAI → typo → gibberish → disposable/role → DNS/MX/null-MX → auth records → SMTP probe → catch-all) emits a **0–100 confidence score** plus a coarse `safe / risky / invalid / unknown` status. The engine runs inside the **`sabitd` server daemon**; you reach it three ways — the **`sabit` CLI**, an **HTTP API**, and an **MCP server** for AI agents — all returning the same result.
[](LICENSE)
[](https://github.com/ruwadgroup/sabit/actions/workflows/ci.yml)
[](https://www.rust-lang.org)
[](docs/mcp.md)
[](https://conventionalcommits.org)
[Getting started](docs/getting-started.md) · [Usage](docs/usage.md) · [MCP](docs/mcp.md) · [CLI](docs/cli.md) · [API](docs/api.md) · [Architecture](ARCHITECTURE.md) · [Docs](docs/README.md)
</div>
## Table of contents
- [Overview](#overview)
- [Features](#features)
- [Why Sabit](#why-sabit)
- [What Sabit is not](#what-sabit-is-not)
- [Architecture](#architecture)
- [The result contract](#the-result-contract)
- [Getting started](#getting-started)
- [Documentation](#documentation)
- [Repository layout](#repository-layout)
- [Roadmap](#roadmap)
- [Responsible use](#responsible-use)
- [Contributing](#contributing)
- [Community & support](#community--support)
- [License](#license)
## Overview
Email verification is sold as a binary checkbox — "is this address valid?" — and that framing is why every vendor's marketed 98–99% accuracy collapses in independent tests. Hunter.io's 2026 benchmark of 15 verifiers on ~3,000 real business addresses found **no tool scored above 75%**; the top three were Hunter (70.00%), Clearout (68.37%), and Kickbox (67.53%). The hard part isn't syntax — it's that catch-all domains make per-mailbox SMTP probing meaningless (15–28% of B2B domains are catch-all), Yahoo returns `250` for every address as an anti-harvesting measure, and probing from a dirty IP just gets you blocklisted.
Sabit treats verification as what it actually is: **a multi-signal probabilistic risk-scoring pipeline, not a yes/no checker.** It layers fast, free, local checks ahead of the slow, fragile, network-bound ones, weighs every signal into a single score, and is honest about uncertainty — `risky` and `unknown` are first-class outcomes, not failures.
Use it however you work: the `sabit` CLI for scripts and one-off checks, the HTTP API for your backend or signup flow, or the MCP server so an AI agent can decide — with evidence — whether an address is safe to email instead of trusting a fabricated binary. Every interface talks to the same `sabitd` engine and returns the same result object.
## Features
- **Layered, free-before-expensive pipeline** — every check that can run offline runs first; the network is the last resort, and a null-MX or no-MX+no-A domain short-circuits to `invalid` before any probe.
- **0–100 confidence score + coarse status** — `safe / risky / invalid / unknown`, modeled on Reacher's taxonomy so existing tooling maps cleanly. You set the threshold per use case (strict for cold outreach, lenient for signup forms).
- **Three interfaces, one result** — a CLI, an HTTP API, and an MCP server all call the same engine and return the same object. The MCP tools (`verify`, `submit_batch`, `task_status`, `auth_check`, `parse_bounce`, `doctor`) are honest by design: a tool never returns `safe` for an address the protocol couldn't confirm.
- **Never sends mail** — the prober opens TCP to the MX, does `EHLO → MAIL FROM → RCPT TO → QUIT`, and reads the reply code. No `DATA` is ever transmitted, so no message is ever delivered.
- **Catch-all aware** — repeated randomized probes detect accept-all domains and downgrade the result to `risky` instead of a false `valid`.
- **Disposable & role detection, kept fresh** — a real community blocklist (7k+ domains) plus MX-fingerprint clustering and role/free-webmail classification; the daemon auto-refreshes the volatile lists from source on a schedule (or `sabit lists update`).
- **Deliverability built in** — SPF/DKIM/DMARC/BIMI reading, DSN/NDR bounce parsing (RFC 3464/3463), and ARF feedback-loop parsing (RFC 5965), not bolted on.
- **Self-hosted and private** — runs on your own unblacklisted VPS; nothing about what you verify leaves your server; no telemetry.
## Why Sabit
The state of the art is mostly closed SaaS that hides its method and oversells its accuracy. The strongest open prior art — Reacher / `check-if-email-exists` — proved the modern technique: SMTP for domains where it still works, headless provider-specific checks where it doesn't, and per-domain rule overrides. Sabit builds on that, adds an explicit weighted scoring model and a deliverability layer, and exposes it through a CLI, an HTTP API, and an MCP server so it fits a script, a backend, or an AI agent equally.
| Principle | How Sabit applies it |
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------- |
| Cheapest signal first | Local layers (syntax/typo/gibberish/disposable) resolve obvious input before any network call |
| Probabilistic, not binary | Every signal is weighted into a 0–100 score; `risky`/`unknown` exist precisely for the gray zone |
| Honest about uncertainty | Catch-all, anti-harvesting `250`s, and greylisting are reported as such — never hidden as `valid` |
| Built to integrate | One result contract over a CLI, an HTTP API, and an MCP server — evidence and confidence, not a fabricated yes/no |
| Responsible probing by default | FCrDNS-checked HELO, real `MAIL FROM`, per-domain caps, backoff, aggressive caching, no `DATA` |
## What Sabit is not
- **Not a sender.** Sabit verifies and scores; it never delivers mail. The deliverability layer parses bounces and reads auth records, but the only thing it sends to a target MX is an envelope it abandons before `DATA`.
- **Not a guarantee.** The only ground truth for deliverability _and_ ownership is double opt-in. Everything else is inference. Sabit is built to express confidence, not to fake certainty.
- **Not a list-buying or scraping tool.** It has no address-generation or harvesting features; dictionary attacks and list scraping are the abuse it is designed _not_ to be.
- **Not a hosted SaaS.** It is software you run. There is no Sabit cloud, no per-check billing, and no telemetry phoning home.
## Architecture
```
CLIENTS (thin — no verification logic, just point at a sabitd over HTTP)
┌──────────────────────────────┐ ┌──────────────────────────────┐
│ sabit-mcp (MCP, for agents) │ │ sabit (CLI: verify/batch/…) │
└───────────────┬──────────────┘ └───────────────┬──────────────┘
└───────────────┬──────────────────┘
HTTPS (REST + task polling)
│
════════════════════════════════▼═══════════════════════════════════ server boundary
┌──────────────────────────────────────────────────────────────────┐
│ sabitd — the daemon (runs where probing is possible: port 25, │
│ clean IP, PTR/FCrDNS). HTTP API + task/job worker (sabit-server). │
├──────────────────────────────────────────────────────────────────┤
│ sabit-core (Rust — the only place behavior lives) │
│ Local • Syntax (RFC 5321) • Typo • IDN→Punycode • Gibberish │
│ • Disposable + MX-fingerprint • Role / free classify │
│ Network • DNS: MX+A/AAAA, null-MX, SPF/DKIM/DMARC/BIMI (hickory) │
│ • SMTP probe: RCPT-TO, no DATA, greylist retry │
│ • Catch-all detection • Per-provider routing │
│ • Bounce/DSN parser • Feedback-loop ingester │
│ Scoring • Weighted 0–100 score → {safe|risky|invalid|unknown} │
└──────────────────────────────────────────────────────────────────┘
```
All verification behavior lives in `sabit-core`, which runs **only inside `sabitd`** — the
server daemon on a host that can actually probe (outbound port 25, a clean IP, matching
PTR/FCrDNS). The `sabit` CLI and `sabit-mcp` are deliberately thin: they translate transport and
relay the result contract, holding no engine logic, so a laptop or an agent host needs nothing
special. The core still splits cleanly into a **local** half (pure, offline, deterministic —
`verify --local`) and a **network** half (DNS + SMTP). Full reasoning is in
[ARCHITECTURE.md](ARCHITECTURE.md).
## The result contract
Every interface returns the same object. A `verify` of one address:
```json
{
"input": "jane.doe@example.com",
"status": "safe",
"score": 96,
"syntax": {
"valid": true,
"normalized": "jane.doe@example.com",
"domain": "example.com",
"is_idn": false,
"is_eai": false
},
"mx": { "accepts_mail": true, "null_mx": false, "records": ["aspmx.l.example.com"] },
"smtp": {
"can_connect": true,
"is_deliverable": true,
"is_catch_all": false,
"has_full_inbox": false,
"is_disabled": false,
"verif_method": "smtp"
},
"misc": { "is_disposable": false, "is_role_account": false, "is_free": false },
"auth": { "spf": true, "dkim": true, "dmarc": "p=reject", "bimi": false },
"signals": { "gibberish": 0.02, "typo_suggestion": null, "greylisted": false },
"took_ms": 812
}
```
`status` is the coarse verdict; `score` is the tunable number; `signals` and the per-layer blocks are the evidence. See the [result object](docs/api.md#the-result-object) and [How it works](docs/how-it-works.md).
## Getting started
No compiler needed — the installer pulls prebuilt binaries.
**1. Run the server (`sabitd`) on a probing-capable host** (port 25 open, clean IP, PTR/FCrDNS):
```bash
curl -fsSL https://raw.githubusercontent.com/ruwadgroup/sabit/main/install.sh | sh -s -- daemon
sudoedit /etc/sabit/sabit.toml # set smtp.helo to this host's FQDN (matching its PTR)
sudo systemctl enable --now sabitd
```
Front it with a TLS-terminating proxy and enable `server.api_key_file` before exposing it. Full
guide: [packaging/README.md](packaging/README.md).
**2. Point a client at it** (laptop / CI / next to an agent):
```bash
curl -fsSL https://raw.githubusercontent.com/ruwadgroup/sabit/main/install.sh | sh -s -- cli mcp
mkdir -p ~/.config/sabit && $EDITOR ~/.config/sabit/config.toml # set your sabitd url + token
sabit doctor # can the SERVER probe? (port 25, PTR/FCrDNS)
sabit verify jane.doe@example.com # one address
sabit batch contacts.csv --out results.jsonl # async task: submit, poll, fetch
# Or use it from an AI agent (Claude Code) over MCP:
SABIT_SERVER=https://sabit.example.com SABIT_TOKEN=sk_live_… \
claude mcp add sabit -- sabit-mcp
```
See [Getting started](docs/getting-started.md) and [Use with AI agents](docs/mcp.md).
## Documentation
| Doc | What you'll find |
| ------------------------------------------ | --------------------------------------------------------------- |
| [Getting started](docs/getting-started.md) | Install the server, configure a client, first verification |
| [Usage](docs/usage.md) | Everyday CLI usage — verify, batch tasks, auth, bounces |
| [Use with AI agents](docs/mcp.md) | The MCP server: setup, tools, a typical agent flow |
| [CLI reference](docs/cli.md) | Every command and flag |
| [HTTP API](docs/api.md) | Endpoints, bearer auth, the result object, errors |
| [Configuration](docs/configuration.md) | Server (`sabit.toml`) and client config |
| [Deployment](docs/deployment.md) | Running `sabitd` in production (port 25, systemd, TLS, scaling) |
| [How it works](docs/how-it-works.md) | The scoring model, the pipeline, and honest uncertainty |
| [Architecture](ARCHITECTURE.md) | The deep design reference |
Start at [docs/README.md](docs/README.md).
## Repository layout
```
sabit/
├── crates/
│ ├── sabit-core/ # the engine: local + network layers + scoring (server-side only)
│ ├── sabit-server/ # HTTP API + task/job worker (lib, used by sabitd)
│ ├── sabitd/ # the server DAEMON — runs the engine + API on a probing-capable host
│ ├── sabit-client/ # thin HTTP client + multi-instance config (shared by the clients)
│ ├── sabit-cli/ # the `sabit` binary — a thin client of a sabitd instance
│ └── sabit-mcp/ # the MCP server — a thin client that exposes sabitd to agents
├── packaging/ # systemd unit, sysusers/tmpfiles, default config, nfpm (.deb/.rpm)
├── data/ # disposable lists, MX fingerprints, rules.json, name corpus
├── conformance/ # fixtures + cross-interface accuracy harness
├── docs/ # everything in the table above
└── .github/ # CI, release, security scanning, templates
```
**Server vs. client.** Verification only happens in `sabitd`, which must run where SMTP probing
is possible — a host with outbound port 25, a clean IP, and matching PTR/FCrDNS (a VPS, not a
laptop). The `sabit` CLI and `sabit-mcp` carry no verification logic; they point at one (or more)
`sabitd` instances over HTTP and relay the result. Install and operate the daemon per
[packaging/README.md](packaging/README.md).
## Roadmap
The engine (local + DNS + SMTP layers, catch-all detection, per-provider routing, scoring), the `sabitd` HTTP API with the async task system, bearer auth, the deliverability parsers (DSN, ARF), and the Linux packaging are in place. On the roadmap: populated community data bundles with a refresh mechanism, an SMTP-verdict cache, a broader per-provider headless module set (Outlook B2C / Yahoo), real STARTTLS upgrade, a Streamable-HTTP MCP transport, and shared/persistent task storage. Full plan: [ROADMAP.md](ROADMAP.md).
## Responsible use
SMTP probing uses the same handshake spammers use to harvest addresses, and aggressive probing can violate a receiving provider's ToS and get your IP blocklisted. Sabit ships responsible defaults — FCrDNS-checked HELO, a real `MAIL FROM`, per-domain rate caps, jittered backoff, and **never sending `DATA`** — but the operator is responsible for lawful use. Verifying a list you already hold for a legitimate purpose is generally defensible; probing arbitrary harvested addresses is not. See [Deployment → responsible probing](docs/deployment.md). This is technical guidance, not legal advice.
## Contributing
Contributions are welcome — especially disposable-domain and MX-fingerprint updates, per-provider rules (`data/rules.json`), accuracy-corpus addresses (with permission), and SMTP edge-case reports. Read [CONTRIBUTING.md](CONTRIBUTING.md) for the invariants, dev setup, and commit conventions ([Conventional Commits](https://www.conventionalcommits.org) with enforced scopes).
## Community & support
- **Bugs & features** — [GitHub issues](https://github.com/ruwadgroup/sabit/issues) (structured templates)
- **Security reports** — privately, per [SECURITY.md](SECURITY.md)
- **Governance** — [GOVERNANCE.md](GOVERNANCE.md)
## License
[Apache-2.0](LICENSE). Sabit bundles community disposable-domain lists and an n-gram name corpus under their own licenses — see [LICENSING.md](LICENSING.md).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessSyncing