
wa — safe WhatsApp automation
io.github.yolo-labzPublished by GitHub account yolo-labzv2.3.0Updated Oct 25, 2026
Safety-first WhatsApp tools: draft-gated sends, enforced rate limits, append-only audit.
Overview
A local MCP server that lets an assistant read and send WhatsApp messages through a safety-gated daemon, with sends drafted for human approval by default.
- What you can do
- Send or draft text and media messages, and schedule sends for later, all through an enforced safety pipeline with a human-review draft queue to list and approve pending drafts. Search synced messages, fetch a chat's recent thread, list active chats, resolve contacts and read group metadata. Wait for a matching inbound reply and transcribe voice notes. Check daemon connection and session status, with read-only and toolset scoping options.
- When to use it
- When you want an AI agent to work with your personal WhatsApp account — drafting or sending messages, searching history, or waiting on replies — while keeping a human approval step and rate limits in place. Not intended for bulk messaging or multi-tenant gateways.
- Requirements
- Runs as a local desktop process (stdio) via a signed bundle or the wa CLI with the mcp serve subcommand. Needs the wa daemon running with a paired WhatsApp Multi-Device session (QR or phone-code pairing) and an allowlist entry granting send/read actions to each recipient. macOS or Linux.
Installation
In SourceWeft
- Open wa — safe WhatsApp automation in the dashboard and add it to a workspace.
- Enable the server for the chats that should use its tools.
Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.
Other MCP clients
Follow the launch instructions in the repository.
README
wa
Personal WhatsApp automation CLI + daemon, written in Go.
A hexagonal Go daemon that owns a WhatsApp Multi-Device session and a thin JSON-RPC client that talks to it — safe enough to let a language model send messages on your behalf, crash-safe enough to survive a power loss mid-migration, and paranoid enough to refuse every destructive flag you might expect.
[CI] [Release] [Go] [License] [Conventional Commits] [Nix flake] [OpenSSF Scorecard] [SLSA L2] [Verified by Sigstore]
Quickstart · Install · Manual · Architecture · Security · Contributing · Português (BR)
Capability
Pattern. Persistent WhatsApp daemon over a Unix socket — one wad process holds the Multi-Device session, ratchet store, and websocket; a thin wa JSON-RPC client invokes it.
Trade-off. ~30 MB RSS per profile and a 5–8 s cold connect, in exchange for sub-second warm-call latency on every subsequent wa send. Per-call session reattachment is avoided entirely.
Use when. A shell pipeline, cron job, or Claude Code plugin needs to dispatch WhatsApp messages with predictable latency and a non-overridable safety pipeline (default-deny allowlist + rate limiter + warmup ramp + append-only audit log) sitting below every RPC path.
Demo
Every frame is real output from the binaries in this repo, recorded against a
throwaway unpaired daemon in a private temp directory. doctor honestly warns
that the device is unpaired (and may flag missing backups in the fresh sandbox).
No real account data is used. The revoke command is help only, not a mutation.
The recording is generated, never hand-edited. The source of truth is the
plain-text docs/assets/wa-demo.tape; regenerate
the GIF, MP4, WebM, static PNG and captured text with one command:
Prefer playback controls or reduced motion? Use MP4, WebM, the static frame, or the text equivalent. Recording notes cover isolation, safety controls, byte budgets and checks. Playback is 1× and silent; no live sends are demonstrated.
How wa compares
Closest peers in the reverse-engineered-WhatsApp ecosystem:
For multi-tenant REST gateways see EvolutionAPI or WAHA — different shape of problem, listed in What this is NOT.
What this is
Two binaries, one repo:
wad— long-running daemon that owns the WhatsApp session, the SQLite ratchet store, and the websocket toweb.whatsapp.com. Runs undersystemd(Linux),launchd(macOS), or a NixOS module. Single-instance per profile, never as root.wa— thin JSON-RPC client that speaks towadover a unix socket. This is what shell scripts, cron jobs, and Claude Code plugins actually invoke.
It is built on go.mau.fi/whatsmeow — the library that powers mautrix-whatsapp at production scale — because it is the only reverse-engineered WhatsApp library actively maintained in 2026. It also ships a safety-first MCP server (wa mcp serve) so an AI agent drives WhatsApp through the same non-bypassable safety pipeline — draft-gated by default. See MCP server.
What this is NOT
- Not a bulk-messaging tool. The rate limiter is non-overridable and there is no
--forceflag anywhere. - Not a multi-tenant SaaS. Each
wainstall is scoped to one person, with optional multi-profile isolation for work/personal splits. - Not a Matrix bridge. Use
mautrix-whatsappif that's what you want. - Not a REST gateway. Use
EvolutionAPIorWAHAif that's what you want. - Not the official WhatsApp Cloud API. This project uses the reverse-engineered Multi-Device protocol via
whatsmeow.
Quickstart
The recipient flag is spelled --to, --jid, or --group depending on the command; --chat <jid> is accepted as a universal alias on all of them (the original flags still work).
For the full tour including multi-profile setup, shell completion, migration, and the audit log, see docs/manual.md.
MCP server
wa mcp serve exposes the daemon to AI agents over the Model Context Protocol (stdio transport). Every tool call runs through the same non-bypassable safety pipeline as the CLI — default-deny allowlist, enforced short-window limits (2/second with burst 2; 30/minute with burst 30; no daily ordinary-send cap) with warmup, and an append-only audit log. Sends default to draft mode: the model proposes a message into a human-review queue and nothing leaves until you run wa draft approve. That draft-gate is the point — it is what makes handing WhatsApp to an autonomous agent safe.
Live on the official MCP Registry as io.github.yolo-labz/wa; one-click install via the signed .mcpb bundle on each release, or wire it manually into Claude Desktop/Code or Cursor:
Tools (12):
It also serves resources (read-only context surfaces) and prompts (pre-built workflows) per the MCP spec. Scope the surface with --toolsets; the send mode is draft (default), direct, or deny, and a read-only set hides every mutating tool.
See docs/mcp-registry.md for the registry publish flow and bundle internals.
Install
Homebrew (macOS + Linux)
Nix flake
NixOS module — import the system module and enable:
home-manager module — import for a per-user installation:
GoReleaser tarball + supply-chain verification
The gh attestation verify step proves that this exact tarball was produced by the yolo-labz/wa GoReleaser job on the exact commit SHA the tag points at. Every release ships:
wa_<version>_<os>_<arch>.tar.gz— the platform tarballchecksums.txt— SHA256 of every artifactchecksums.txt.sigstore.json— Cosign-signed Sigstore bundle forchecksums.txt(legacy/optional verify path; not needed when usinggh attestation verifyagainst the artifact above)sbom.cdx.json— CycloneDX 1.6 SBOM (full repo, syft)sbom.spdx.json— SPDX 2.3 SBOM (full repo, syft)sbom.gomod.{wa,wad}.cdx.json— Go-native CycloneDX SBOMs (cyclonedx-gomod, per binary, with stdlib + license info)openvex.json— OpenVEX 0.2 statement file. VEX-aware scanners (Trivy, Grype) consume this to filter "is this CVE actually exploitable inwa?" — most transitive CVEs arenot_affectedbecause thedepguard-enforced port boundary keeps them off the runtime path. Statements grow as findings emerge; current baseline is empty pending per-CVE triage.CHANGELOG.md— git-cliff generated changelog
darwin users — unsigned release: releases cut without an Apple Developer account ship the darwin-arm64 binary unsigned and un-notarized. Gatekeeper will quarantine it on first launch. Strip the quarantine flag before running:
Or install via the Homebrew tap (
brew install yolo-labz/tap/wa) — brew rebuilds from source locally and bypasses the quarantine entirely. Signed + notarized darwin binaries return on the next GA tag cut afterAPPLE_DEVELOPER_ID_APPLICATION_{CERT,KEY}secrets are populated.
go install
The module path includes the /v2 suffix per Go's semantic import versioning rule for v2+ releases.
go install on a tag does NOT inherit the GoReleaser build flags (-trimpath, -buildvcs=true, ldflag-stamped version). The resulting binary will run, but wa --version reports (devel). Use the GoReleaser tarball or the Homebrew tap if version-stamped, reproducible binaries matter to you.
Multi-profile
Run two WhatsApp accounts side-by-side — personal and work, or one per client — with full process isolation. Each profile has its own session.db, allowlist.toml, audit.log, rate limiter, unix socket, and warmup timestamp.
Profile selection precedence (highest wins):
--profile <name>flagWA_PROFILEenv var (empty = unset)$XDG_CONFIG_HOME/wa/active-profilepointer- Singleton (if exactly one profile exists)
- Literal
default
See docs/manual.md §4.
Safety
wa is safe enough to let a language model invoke it on your behalf. The safety pipeline is non-overridable and lives inside the daemon, below every RPC path:
- Allowlist, default-deny. Per-action (
read/send/group.add/group.create). Hot-reloaded on SIGHUP. Mutated viawa allow add/remove. - Rate limiter. Ordinary sends are capped at 2/sec (burst 2) and 30/min (burst 30), with no daily ordinary-send cap. No
--forceflag. Ever. - Warmup ramp. Fresh sessions run at 25 % caps while age is <7 days, 50 % from age 7 through <14 days, and 100 % from age 14 days onward. Timestamp sourced from the session store so daemon restarts don't reset the clock.
- Audit log. Append-only JSON Lines at
$XDG_STATE_HOME/wa/<profile>/audit.log. Every send + every allowlist decision + every migration is recorded. Never auto-rotated — back it up yourself. - Inbound prompt-injection firewall. Inbound message bodies are wrapped in
<channel source="wa" ...>…</channel>tags before reaching Claude Code so the model can structurally distinguish "the user typed this in the terminal" from "an unknown contact sent this". - Crash-safe migration. 007→008 migration uses a write-ahead marker + single
os.Renamepivot + fsync barriers. Proven by a subprocessSIGKILLinjection test that kills the process between every step and asserts zero data loss on recovery. - Socket hardening. Unix socket + sibling lockfile opened with
O_NOFOLLOW(CVE-2025-68146), parent directory verified mode0700+ euid-owned,SO_PEERCREDcheck on every accept, umask-narrowed bind.
Full threat model: SECURITY.md.
Architecture
- Hexagonal core (
internal/domain/,internal/app/) depends only on the 9 port interfaces ininternal/app/ports.go. Not one import ofgo.mau.fi/whatsmeowoutside the adapters, enforced by agolangci-lint depguardrule that is CI-blocking. - Port-boundary fakes: every secondary adapter ships an in-memory twin for tests. The contract test suite (
internal/app/porttest/) runs against any adapter. No test reaches the real websocket. - One daemon per profile. Feature 008 bounds the blast radius: a crash in one profile's daemon never touches another. Trade-off: ~30 MB RSS per profile.
- No CGO. Ever.
modernc.org/sqliteis the only SQLite path. Enforced in the Nix flake (env.CGO_ENABLED = "0"), in GoReleaser, and in thego.modtoolchain flags.
For the full design and reliability principles, see docs/manual.md.
Command reference (at a glance)
Client (wa):
Daemon (wad):
Full flag reference: docs/manual.md §6.
Inspecting the daemon (when the CLI "lies")
The daemon owns the SQLite truth; the CLI is a thin client over it. When a command's output looks wrong — a chat you expect is missing, a message count seems stale — work down this ladder. The first rung resolves almost every case without touching the database.
1. First-class discovery commands (the supported path)
These read straight from the daemon and need no SQL. They replace the old
docker cp messages.db + hand-rolled SELECT workaround:
Add --json to any of them for NDJSON you can pipe to jq. Against a Dokku
deploy, run the same commands inside the container —
dokku enter <app> -- /usr/local/bin/wa chat list — or over an
SSH-forwarded socket with scripts/wa-remote chat list.
2. wa doctor
If the discovery commands themselves look impossible (e.g. zero chats on a
paired account), run wa doctor. It runs 11 checks — socket perms, pairing
state, on-disk layout version, audit-log size — and prints a hint: line for
every WARN/FAIL. A schema_version WARN means the on-disk layout drifted
from domain.LayoutSchemaVersion; run wa migrate to forward-migrate.
3. Raw SQLite (last resort)
The production image is gcr.io/distroless/static-debian12:nonroot — no
shell, no sqlite3, no ls/find. You therefore cannot
dokku enter <app> -- sqlite3 …, and there is intentionally no wa-debug
sidecar image (it would widen the attack surface the distroless base exists
to shrink). Instead, query a WAL-safe snapshot from the Dokku host, which
has its own sqlite3:
On-disk layout (container path → Dokku host path under
/var/lib/dokku/data/storage/<app>):
Copying a live WAL-mode DB with cp can produce a corrupt file — always go
through .backup. See the Backups section of
docs/deploy/dokku.md.
Development
Workflow: every change lands via PR against main, commit subjects follow Conventional Commits. See CONTRIBUTING.md.
CI/CD runs on a self-hosted GitHub Actions runner pool (label set [self-hosted, dokku], with one runner — wa-sonar-runner — additionally labelled sonar). Required-check jobs: detect, lint (golangci-lint), test (race + shuffle + coverage), sonar (SonarQube scan, consumes the cover.out artifact), nix (nix flake check + nix build .#default + smoke), commitlint (PR title), Reproducibility (two-build byte-identity), OSV-Scanner (vuln DB + Go call-graph reachability via internal govulncheck), gitleaks (secret scan), CodeQL (Go + actions). Hosted runners are reserved for OpenSSF Scorecard (needs fresh image guarantees). Release workflow triggers on v* tags and publishes GoReleaser tarballs + dual SBOMs (CycloneDX 1.6 + SPDX 2.3 + Go-native per-binary) + Cosign Sigstore bundles + GitHub-native attestations to GitHub Releases. Apple notarization and Homebrew tap publication graceful-degrade when their secrets are absent.
Project status
Live backlog (deferred until a profile pin or external action exists): homebrew_casks: migration (replaces brews:, but Linuxbrew loses formula path on darwin-only casks — needs apt/rpm via nFPM as a Linux substitute first), LID/BSUID dual-keying in domain.JID (Meta June 2026 username rollout), default.pgo PGO profile (capture pipeline + commit), actions/attest migration off the attest-build-provenance@v4 wrapper.
Who this is for
One person — the maintainer. Multi-tenancy, hosted SaaS, and group-bulk-messaging use cases are explicitly out of scope. The entire safety story assumes a single-user threat model where FileVault / LUKS is the encryption boundary and wa panic is the recovery button. If that's not what you want, use one of the alternatives listed in What this is NOT.
License
Apache-2.0. The go.mau.fi/whatsmeow upstream is MPL-2.0, which is file-level copyleft and does not propagate to consumers (Mozilla MPL FAQ Q9–Q11). The Apache choice matches the precedent set by Anthropic's official Telegram channel plugin and gives an explicit patent grant.
Acknowledgements
tulir/whatsmeow— Tulir Asokan and the mautrix project, for the only WhatsApp library worth using in 2026.AsamK/signal-cli— for proving that a daemon-plus-thin-CLI architecture for an end-to-end-encrypted messenger is achievable in a single binary.aldinokemal/go-whatsapp-web-multidevice— closest prior art, solving a different shape of the problem (REST gateway vs CLI daemon).spf13/cobra+creachadair/jrpc2+modernc.org/sqlite— the three load-bearing Go libraries that make this project CGO-free, testable, and pleasant to maintain.rogpeppe/go-internal— forlockedfileandtestscript, both of which the project leans on heavily.
See also (yolo-labz ecosystem)
- yolo-labz/claude-mac-chrome — Chrome automation for cross-app pipelines (e.g., LinkedIn DM → triage → WhatsApp follow-up via this daemon).
- yolo-labz/linkedin-chrome-copilot — LinkedIn workflow plugin that composes with
wafor cross-channel automation. - yolo-labz/kokoro-speakd — TTS daemon for spoken status feedback during long-running send batches.
- Architecture deep-dives + WhatsApp-daemon design rationale: blog.home301server.com.br.
- Author portfolio: portfolio.home301server.com.br.
Services
Compliance-grade AI architecture for regulated workloads — async-first, USD-denominated, LATAM-based / EN-fluent. See blog.home301server.com.br/services.
Source: README.md at commit d2efe6d
Tools
0Version history
1- v2.3.0LatestSep 16, 2026
Score
50% of the applicable checks met
How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.
- Reached in the last 30 days25 pts
Keep the endpoint answering the MCP handshake; SourceWeft probes it every 30 days.
- Auth declared10 pts
Declare the credential headers in the registry entry, or keep the endpoint answering the probe so its auth can be classified.
- Tools listed15 pts
Answer tools/list without a credential, or ship a package that starts in a sandbox without one.
- DescriptionRequired15 pts
- Logo10 pts
- Documentation15 pts
- License10 pts