Repository navigation
Releases
v0.7.2
Behaviour change for download_media with output_path: the call now writes a separate copy instead of a hard link to the media cache, so editing an exported file can no longer change the cache. An existing file at output_path is never replaced. If it already holds this message's media the call succeeds without downloading again; otherwise it fails and asks you to choose a new path or remove the file. Exported media now uses its own disk space.
What's Changed
- fix: isolate download output from the media cache by @jay23606 in #117
- fix(client): return verified output_path without re-downloading by @Sealjay in #120
Full Changelog: v0.7.1...v0.7.2
v0.7.1
What's Changed
- docs: position WhatsApp MCP around its current capabilities by @Sealjay in #113
- perf: speed up multi-arch container publishing by @Sealjay in #114
- fix: import poll votes from synchronized history by @jay23606 in #115
- fix(client): tally history poll votes that arrive with their poll by @Sealjay in #116
New Contributors
Full Changelog: v0.7.0...v0.7.1
v0.7.0
What's Changed
- docs: consolidate repository guidance by @Sealjay in #88
- fix(logs): redact chat names, contact names and JIDs in client logs by @benjamin-di in #75
- fix(client): unwrap history-sync messages before storing them by @benjamin-di in #64
- fix(events): store captions of incoming image, video and document messages by @benjamin-di in #58
- fix(send): rate-limit replies and reactions, cache sent replies by @benjamin-di in #79
- feat(store): include message ID on every list_messages line by @zaeeeeeem in #82
- Store incoming contact cards as text by @MarioCuautle in #55
- Cover history-sync group sender recovery by @Sealjay with @Copilot in #89
- chore: update whatsmeow dependency by @Sealjay with @Copilot in #90
- Publish release container images to GHCR by @Sealjay with @Copilot in #91
- feat: enforce read-only WhatsApp tool access by @Sealjay in #93
- Pin Docker base images and upgrade inherited runtime packages by @Sealjay with @Copilot in #92
- Add opt-in voice-note transcription via an OpenAI-compatible speech-to-text endpoint by @MarioCuautle in #56
- fix: require HTTPS for remote transcription services by @Sealjay in #94
- feat(pairing): opt-in full history sync when linking a device by @benjamin-di in #60
- feat: expose daemon health and connection readiness by @Sealjay in #96
- Add read-only WhatsApp Business label support by @MarioCuautle in #53
- docs: remove retired report badge and link tool count by @benjamin-di in #98
- docs: refresh guidance and correct project badges by @Sealjay in #99
- fix(pairing): start a new pairing flow after the previous one expired by @benjamin-di in #77
- feat(media): recover expired media links by asking the phone to re-upload by @benjamin-di in #73
- feat(client): apply incoming edits and deletes for everyone to the cache by @benjamin-di in #71
- Potential fix for code scanning alert no. 1: Workflow does not contain permissions by @Sealjay in #100
New Contributors
- @benjamin-di made their first contribution in #75
- @zaeeeeeem made their first contribution in #82
- @MarioCuautle made their first contribution in #55
Full Changelog: v0.6.0...v0.7.0
v0.6.0
v0.5.0
New tools and features
- Docker support. A multi-stage
Dockerfilebuilds ongolang:1.26-bookwormand ships ondebian:bookworm-slimwith ffmpeg and CA certificates, running as a non-root user and binding to loopback by default. The README covers pairing through a one-off container beforeservestarts, the-allow-remote+WHATSAPP_MCP_TOKENoverride for publishing the port, and re-pairing after WhatsApp's session rotation. (#39, thanks @mikhail-koviazin)
Fixes
- Native Windows builds work again. The store lock used
syscall.Flock, which doesn't exist on Windows. Unix and Windows now have separate implementations, with aLockFileEx-based Windows path and new tests. (#35, thanks @mikhail-koviazin) download_mediano longer returns the wrong file. The cache was keyed by filename, so two messages with the same filename could overwrite each other. Entries are now keyed by message ID and checked against the stored size and SHA-256 before use. (#32, thanks @alexbonea)request_synccan walk history backwards. It used a cursor WhatsApp's servers couldn't resolve, so requests silently returned nothing. It now starts from a real cached message (oldest, newest, or nearest a timestamp) and takes newanchor,countanduse_lidparameters, including a fallback for history from before the LID migration. (#33, thanks @NellySsenkumba)/pairno longer rate-limits itself. The page refreshes every 5 seconds, and its limiters refilled more slowly than that, so one open page ran out of allowance and got stuck on 429. The limiters now refill faster than the page polls. (#37, thanks @mikhail-koviazin)- whatsmeow bumped, with
SetStatusMessageadapted to upstream's newSetStatusInputargument. (#29) - Claude Desktop config example fixed.
claude_desktop_config.jsononly launches stdio servers, so the README now bridges to the daemon withmcp-remote. (#30, thanks @kbullett)
Breaking changes
request_syncnow requireschat_jid, andClient.RequestHistorySynctakes newanchor,countanduse_lidparameters. (#33)
Internal
- Tests for
request_syncargument parsing and a regression test for the/pairrate-limit fix.
Full Changelog: v0.4.1...v0.5.0
v0.4.1
Fixes
-
Group and blocklist tools now accept
+-prefixed phone numbers.add_group_participants,remove_group_participants,create_group,block_contactandunblock_contactparsed recipients through a separate code path that never stripped a leading+or rejected non-digit input, so an E.164 number like+447967960994silently misrouted. This is the same bug fixed for the send tools in #16, which those paths never picked up.Fixed at the root rather than per-tool: the duplicate parser is deleted, so every recipient in the codebase now goes through one
NormalizeRecipient. Whitespace trimming moved there too, which the send path gains as a side benefit.
Internal
- The weekly upstream probe now reports an available whatsmeow bump to a single rolling issue instead of echoing into the Actions run log where nobody reads it. It stays silent when the pin is already current.
v0.4.0
First release since v0.3.0 (11 May 2026).
New tools and features
pairing_statustool — MCP clients can poll a structured setup_state envelope (ready / awaiting_qr with QR payload / error) to drive their own pairing UI, instead of shelling out to the/pairweb page.- Media resources at
whatsapp://URIs — any MCP client can fetch the decrypted bytes of a media message via the standardresources/readcall (whatsapp://media/{chat_jid}/{message_id}), with no size cap — covers videos and large documents that don't fit inline. - Inline media in
download_media— image and audio messages up to 5 MiB now come back as inline image/audio content alongside the JSON summary, so remote MCP clients can view or hear media without an out-of-band file transfer. Video and documents are unchanged and still resolve to a file path. - Send/usync rate limiting — the daemon now throttles outgoing messages (45s floor, 30/hour, stricter for first-contact sends) and
is_on_whatsapplookups (3/minute), to stop a runaway or misbehaving client getting the paired account rate-limited or banned by WhatsApp. Operators can override per-request via an HTTP header for legitimate bursts. - Passkey pairing support — picked up from upstream whatsmeow, alongside ongoing WhatsApp protocol updates.
Fixes
- Contact names now resolve from the synced address book —
search_contactsand sender-name display previously only looked at the bare phone number stored per-chat; saved names from your phone's address book were ignored. Both now prefer the real name. (Closes #21, reported by @sidjainn.) - Media downloads no longer 403 — a URL-parsing bug was dropping WhatsApp's signed CDN auth parameters, so every media download failed. Downloads now also use WhatsApp's own DirectPath field directly instead of re-deriving it from the URL, making them more robust going forward.
- No more startup crash-loop on network-mounted stores — pointing
-storeat an Azure Files SMB share or an ACA EmptyDir mount previously crashed the daemon on a permissions check those mount types don't support. The check is now best-effort on such mounts.
Internal / maintenance
- Two tech-debt passes across the client, store, daemon, mcp and security packages removed dead code and unused generality (336 lines deleted, 278 added back as shared helpers), with no behaviour change.
- All 42 MCP tools now explicitly declare their readOnly/destructive/idempotent/openWorld hints.
- CI no longer double-runs on branch pushes; markdown-only changes skip CI.
- whatsmeow bumped to 2026-07-18; mcp-go bumped 0.48.0 → 0.56.0; assorted transitive dependency bumps.
Upgrade notes
- No action required. The one new store column (media DirectPath) is nullable and auto-populated going forward, with existing rows still working via the old fallback — no schema migration step, re-pairing, or deleting
store/*.dbneeded. - mcp-go 0.56 adds automatic DNS-rebinding protection: the daemon now rejects requests whose
Hostheader isn't recognised as loopback (403). If your MCP client connects via a hostname behind a proxy rather than127.0.0.1directly, point it at127.0.0.1. - Building from source now requires Go 1.25.5+ (was 1.25.0), forced by mcp-go's own module requirements.
v0.3.0
v0.2.1
Security Hardening
- Fix downloaded media file permissions from 0644 to 0600
- Cap
list_messageslimit to 100,context_before/context_afterto 20 - Sanitise ffmpeg stderr to strip file paths before returning to MCP callers
- Add CSRF token validation on
/pair/reset(single-use tokens) - Resolve "localhost" via DNS in
isLoopbackAddrinstead of trusting the string literal
Full Changelog: v0.2.0...v0.2.1