wazap

MCP serverCommunication

wazap connects your AI to WhatsApp so it can take care of your chats for you. Once added, your AI can read and send messages, share media, and keep up with your contacts and groups without you opening the app.

Unavailable. This server has no hosted endpoint yet, so Ahel can't serve it.

Add to setup to save this item as a reference. Ahel cannot run it, and signing in will not install it.

What your AI can do with it

  • Read and send WhatsApp messages
  • Browse your chats and pick up conversations
  • Share and receive media
  • Look up your contacts
  • See and take part in group chats

Getting started

  1. Save this item in Your setup as a reference.
  2. Read the source or reference documentation for its setup requirements. Saving it here does not connect it to your AI.
  3. Check this page for availability before trying to install it through Ahel.

From the project's README

As published by razvangirgiz/wazap in README.md.

██╗    ██╗ █████╗ ███████╗ █████╗ ██████╗
██║    ██║██╔══██╗╚══███╔╝██╔══██╗██╔══██╗
██║ █╗ ██║███████║  ███╔╝ ███████║██████╔╝
██║███╗██║██╔══██║ ███╔╝  ██╔══██║██╔═══╝
╚███╔███╔╝██║  ██║███████╗██║  ██║██║
 ╚══╝╚══╝ ╚═╝  ╚═╝╚══════╝╚═╝  ╚═╝╚═╝

WhatsApp for your AI agent. An MCP server that puts your WhatsApp account — chats, messages, media, contacts, groups — behind 33 tools any MCP client can call. Pairing-code login, no browser, no phone-number reseller, ~20 MB of RAM.

Built on Baileys, which speaks the WhatsApp multi-device protocol over a WebSocket.

Get started

The npm package is wazap-mcp; the command it installs is wazap.

npx wazap-mcp setup

That is the whole install. It links your account, finds the MCP clients installed on this machine, writes their config, copies the five skills where that client reads them, and tells you what to restart. At a terminal it is one black, centered screen per step: ghosted ASCII logo, step number, then the QR or the question. Piped output stays a log. When you started through npx, setup offers to install wazap globally so Claude Desktop and the background service have a path that does not change. It also offers to brew install whisper-cpp, ffmpeg or Tailscale when a step needs one and it is missing, and to restart Claude Desktop itself once it has connected it.

Or the path your harness prefers

HarnessFastest path
Claude Code/plugin marketplace add razvangirgiz/wazap, then /plugin install wazap@wazap
Claude Desktopdownload wazap-<version>.mcpb from Releases and double-click it
Gemini CLIgemini extensions install https://github.com/razvangirgiz/wazap
Cursorthe Install in Cursor badge, then npx wazap-mcp skills install cursor
VS Codethe Install in VS Code badge
Codex CLInpx wazap-mcp connect codex, then npx wazap-mcp skills install codex
OpenCodenpx wazap-mcp connect opencode, then npx wazap-mcp skills install opencode
Windsurfnpx wazap-mcp connect windsurf
Grok BotGrok Bot / remote MCP
Anything elsethe MCP entry npx -y wazap-mcp over stdio, or a self-hosted URL

Each local harness registers the server. Grok Bot is a URL you paste. Linking the WhatsApp account is a separate, one-time step: npx wazap-mcp login.

Or have your agent do it. Paste this:

Set up WhatsApp for me: run npx wazap-mcp setup --agent and follow what it prints.

Then ask your agent: "what did I miss on WhatsApp today?"

Below are the steps setup runs for you. Each is still its own command when you want to run it by hand.

npx wazap-mcp login shows a QR code; scan it from Settings → Linked devices → Link a device. No camera handy, or linking over SSH? npx wazap-mcp login --phone +15550100 prints an 8-character code you type under Link with phone number instead. It ends by asking whether the agent may send messages; the answer is no unless you say yes, and npx wazap-mcp config writes on changes it later.

npx wazap-mcp connect claude-code writes the MCP entry for one client. The table under Connect a client has the rest.

npx wazap-mcp on its own is safe to run: it prints where you stand and what to do next, and starts no server. When something is off, npx wazap-mcp status is the first thing to run — it checks Node, the data directory, the lock, the credentials and whether a newer version is out, and prints the fix next to anything broken.

Connect a client

wazap connect <client> writes the entry for you, keeping whatever else is in the file and backing it up once before the first change. --dry-run shows what it would write.

ClientWhat connect writes
claude-coderuns claude mcp add whatsapp for you
claude-desktopclaude_desktop_config.json in the Claude application directory
cursor~/.cursor/mcp.json
codex[mcp_servers.whatsapp] in ~/.codex/config.toml
vscode./.vscode/mcp.json, for the current workspace
gemini~/.gemini/settings.json
windsurf~/.codeium/windsurf/mcp_config.json
opencodemcp.whatsapp in ~/.config/opencode/opencode.json
Grok Botclient's MCP URL field: http://<host>:<port>/mcp with header Authorization: Bearer <token> (see Grok Bot / remote MCP)
anything remoteclient's MCP URL field: https://your-host/mcp with header Authorization: Bearer <token>, or just the URL once OAuth is on (see Self-host)

Grok Bot / remote MCP

Grok Bot is an HTTP MCP client. It does not launch wazap over stdio.

  1. On the machine that will run wazap, npx wazap-mcp login until the CLI says the account is linked. get_status and link_account are MCP tools; they need HTTP already serving (step 3) and Grok already connected (step 4).
  2. Answer writes yes or no at login. Writes stay on when WAZAP_READ_ONLY is unset. A Bearer write token is not writes being enabled. If write tools are missing, you need writes on (wazap config writes on and restart) and a write Bearer on this session. Config alone is not enough on HTTP.
  3. Serve HTTP with a read token:
WAZAP_READ_TOKEN=$(openssl rand -hex 32) \
npx wazap-mcp serve --http

Set WAZAP_WRITE_TOKEN too only if this client should send, and put that value in the header in step 4.

  1. In Grok Bot, add an MCP server at http://<host>:<port>/mcp with header Authorization: Bearer <token>. Then call learn, then get_status. connected means the WhatsApp socket is up. This session can send only when write tools are registered (write_tools: true, or send tools appear in the tool list). Then read.

wazap setup asks Remote client (Grok Bot / HTTP MCP)? and prints the same URL and header. Answering yes does not start expose.

Other MCP clients

Cursor and VS Code install from a link:

Both carry the same entry connect writes. Where a custom scheme is stripped before you can click it, VS Code also takes the https form. node scripts/badges.mjs reprints all three.

Any other MCP client works the same way: the command is npx -y wazap-mcp, the transport is stdio. Tell the agent to call learn first — it returns the id formats, the workflows and every error code with what to do about it.

{
  "mcpServers": {
    "whatsapp": {
      "command": "npx",
      "args": ["-y", "wazap-mcp"]
    }
  }
}

Claude Desktop, Cursor, Gemini CLI and Windsurf take exactly that. VS Code nests it under servers and wants a "type": "stdio" alongside command. Codex CLI is TOML:

[mcp_servers.whatsapp]
command = "npx"
args = ["-y", "wazap-mcp"]

OpenCode takes the command and its arguments as one array, under mcp:

{
  "mcp": {
    "whatsapp": { "type": "local", "command": ["npx", "-y", "wazap-mcp"] }
  }
}

The skills/ folder follows the Agent Skills format, so Codex, Cursor and other skill-aware agents can load the same five skills.

Gemini CLI

gemini extensions install https://github.com/razvangirgiz/wazap

That reads gemini-extension.json at the repo root, so it registers the MCP server and loads GEMINI.md — the five skills below, concatenated, because the Gemini CLI takes one context file per extension rather than a skills directory. wazap connect gemini writes the server alone, without the context.

GEMINI.md is generated: npm run context:build rebuilds it from skills/*/SKILL.md, and a test fails if the two have drifted, so a workflow is only ever edited in its skill.

Claude Desktop, without a terminal

Download wazap-<version>.mcpb from Releases and double-click it. Claude Desktop installs the server, its Node dependencies and the icon, and shows two settings: Read-only, ticked, and Data directory, empty. wazap connect claude-desktop does the same job by editing claude_desktop_config.json. Claude Desktop starts its servers without your shell PATH, so that entry is the absolute path to node when wazap is installed globally, and npx otherwise; wazap setup checks that the entry it wrote is one Claude Desktop can actually launch.

Then ask Claude to link your WhatsApp. It calls link_account with your number, hands back an 8-character code, and you type that code into WhatsApp → Settings → Linked devices → Link a device → Link with phone number instead. No terminal at any point. npx wazap-mcp login does the same job from a shell when you have one.

Untick Read-only to let Claude send. It ships ticked because a bundle that can message people from your number before you have said so is the wrong default, and because the setting cannot be left unanswered: the manifest format has no way to omit an argument, so the box you see is the answer the server gets. link_account is registered either way. Read-only exists to stop Claude messaging people from your number, and relinking your own dead session messages nobody.

Build it yourself with npm run bundle:mcpb, which stages dist/, the manifest, the icon and a fresh production node_modules, then packs them with @anthropic-ai/mcpb.

Keep it running

A wazap started by a client lives as long as that client does. Quit Claude Code and the session is gone until you open it again. Two commands change that. Staying up and being reachable are separate choices.

npx wazap-mcp service install

That writes a launchd agent on macOS (~/Library/LaunchAgents/com.wazap.server.plist) or a systemd user unit on Linux (~/.config/systemd/user/wazap.service), starts it, and waits for /healthz to answer. The unit runs serve --http on 127.0.0.1:8766 with the absolute path of this Node and this install, so it survives a reboot and a logout. Point any client at http://127.0.0.1:8766/mcp, or keep using the stdio entry. A second wazap on the same data directory becomes a bridge onto the session this one holds.

service status prints the pid, the health check and whether the unit still runs the version you have installed. service logs tails it. service restart picks up an upgrade; service uninstall removes the unit and leaves your session and credentials alone. wazap login needs the session to itself, so it stops the service, pairs, and starts it again on its own.

A sleeping Mac is an offline wazap. System Settings → Lock Screen, or Battery → Options, has the switch that keeps it awake on power.

npx wazap-mcp expose

That gives the running service a public https URL, for agents that are not on this machine: a cloud agent, claude.ai, ChatGPT. It uses Tailscale Funnel if tailscale is installed, Cloudflare Tunnel if cloudflared is, opens the tunnel, writes WAZAP_PUBLIC_URL and a fresh WAZAP_OAUTH_PASSWORD into <data-dir>/.env, restarts the service and checks the URL from here. It then prints the MCP URL and the password once.

Give an agent the URL only. It signs in on a consent page on your own host with that password and picks read or read-and-send there; wazap status lists who holds a grant. See Hosted agents (OAuth) for what that page does. npx wazap-mcp expose off takes the tunnel down and keeps the password, so the next expose hands agents the same one.

npx wazap-mcp setup asks all of this once, as its fourth step.

Upgrade

npx wazap-mcp update

One command for what used to be three. It compares this install against the registry, installs the new package when wazap is global, restarts the service so it runs the new code, and copies the new skills into every harness that keeps them. --dry-run prints the plan and touches nothing.

Tools

ToolKindWhat it does
learnreadThe guide to every tool, id format and error code. Call it first.
get_statusreadConnection status, sync state, linked account, named-contact count, versions, data dir. Top-level fields are the default account; accounts lists every live one. Optional account_id on this and every other tool.
list_accountsreadEvery configured account: id, name, status, masked phone, owner name, writes policy. Call this first when more than one account is linked.
link_accountreadPair an account that already exists (wazap account add). Returns the code to type into the phone. Registered in read-only mode too.
list_chatsreadConversations newest-first; filter all/unread/groups/individual/archived.
read_messagesreadMessages in a chat; before pages further back, pulling older history from the phone; types narrows to one or more message types, e.g. ["call"]; include_previews attaches a small image of each photo.
get_recent_messagesreadEverything from the last N hours, grouped by chat. The catch-up tool. include_system adds WhatsApp's own notices, types narrows to one or more message types, include_previews attaches a small image of each photo, compact halves it for a routine catch-up.
get_unansweredreadWho is waiting on the user: chats whose last word is theirs and asks for something, with the ask quoted. Groups only when the user was @-mentioned or replied to.
set_contact_notelocalRemember something about a person, on this machine only; it then shows next to their name everywhere.
mark_handledlocalTake a chat off get_unanswered until the other side writes again. Nothing changes on WhatsApp.
get_storiesreadThe stories (status updates) received in the last day, by author, with previews on request. They show nowhere else.
wait_for_messagesreadBlock up to 55 s until a message arrives, then return it with a cursor for the next call. addressed_to_me wakes only for direct messages, @-mentions and replies.
search_messagesreadText search across the locally held messages; since, until and from narrow it.
get_messagereadOne message in full, with its quoted message and reactions.
search_contactsreadFind contacts by name or number.
sync_contactsreadFetch the phone's address book from WhatsApp again, when names are missing.
get_contactreadName, number, about text, profile picture.
get_group_inforeadParticipants, admins, announcement mode, invite link (when you are admin).
download_mediareadSave an attachment to disk; small images also come back inline.
transcribe_audioreadTurn a voice note or audio message into text, with the local or the API provider.
send_messagewriteDraft text, optionally as a reply, with @-mentions. Does not send.
send_mediawriteDraft an image, video, audio, voice note, document or GIF (as_gif: an mp4 loops, a .gif is converted with ffmpeg) from a path or URL. Does not send.
send_pollwriteDraft a poll with 2–12 options. Does not send.
send_locationwriteDraft a map pin. Does not send.
edit_messagewriteEdit your own message, within WhatsApp's 15-minute window.
react_to_messagewriteAdd or remove an emoji reaction.
forward_messagewriteDraft a forward to another chat. Does not send.
confirm_sendwriteSend a draft after the user has seen the preview and said yes.
delete_messagewriteRetract your own message, within WhatsApp's 2-day window.
set_profile_picturewriteSet the linked account's own profile photo from a local path or URL. Hits WhatsApp immediately.
manage_chatwriteArchive, pin, mute (8h by default), mark read/unread.
create_groupwriteCreate a group and add participants.
manage_groupwriteAdd, remove, promote, demote, leave, rename, invite links.

Seeing, waiting, following up

include_previews: true on get_recent_messages or read_messages attaches a small JPEG of each photo as an image block, newest first, up to 12 per call, and labels each message line with the preview it belongs to, so a catch-up can say "a photo of a receipt" without a download. WhatsApp used to ship such a preview inside every image message and in 2026 almost never does, so when none is there wazap downloads the photo once, shrinks it to 320 px on this machine with pure JavaScript, and keeps the result as a file under previews/ in the data directory, so a restart does not redo it. A video gets one frame, taken by ffmpeg a second in, when ffmpeg is installed. The first call over a day of photos takes a few seconds; the next is instant.

wait_for_messages blocks until something arrives, up to 55 seconds, then returns it with a cursor. Calling it again with that cursor replays whatever landed in between, so an agent can sit in a loop and miss nothing. With addressed_to_me only direct messages, @-mentions of the user and replies to their messages wake it; group chatter does not. The user's own messages and WhatsApp's notices never do.

get_unanswered returns the chats whose last word is the other side's and reads as an ask: a question mark, a request word, or a voice note nobody has heard yet. "Ok, thanks" is not an ask, a link is not a question, and an ask older than two weeks (max_age_hours) was abandoned rather than left waiting. People come first, then the oldest wait, each with the ask quoted and how long they have been waiting; a WhatsApp Business account is marked, since its asks are often automatic replies.

Every message comes back with a non-empty text: media and system messages carry a placeholder such as [image] caption, [voice message · 0:42], [deleted] or [poll] Pizza or pasta?. Timestamps are ISO 8601 with the machine's UTC offset, alongside a human age like 2h ago.

Voice messages

A voice note is the one message an agent cannot read. Switch transcription on and it becomes text: [voice message · 0:42] "sunt la notar, ajung în 20 de minute", with the bare words also in a transcript field. get_recent_messages and search_messages see that text, so a voice note becomes findable by what was said in it.

Pick a provider once, in wazap setup or later:

wazap config transcribe local     # free and private, one 574 MB model on disk
wazap config transcribe openai    # cheap and fast, the audio leaves this machine
wazap config transcribe off
localopenai
Runswhisper.cpp, hereany OpenAI-compatible /audio/transcriptions
Costsnothingper minute of audio, on your key
Privacythe audio never leaves this machinethe audio leaves this machine
Needswhisper-cpp and ffmpeg, plus a modelan API key

Local, with whisper.cpp

brew install whisper-cpp ffmpeg      # macOS; elsewhere build whisper.cpp, install ffmpeg from your package manager
wazap transcribe download            # fetch and verify the model
wazap transcribe test recording.ogg  # prove it before you trust it

wazap setup and wazap transcribe download offer that brew install themselves when either binary is missing, and go straight on to the model in the same run. --no-brew turns the offer off everywhere.

Models land in <data-dir>/models/ and are checked against a SHA-256 pinned in the source; an interrupted download resumes where it stopped.

WAZAP_WHISPER_MODELFileSize
turbo (default)ggml-large-v3-turbo-q5_0.bin574 MB
large-v3ggml-large-v3-q5_0.bin1.08 GB
mediumggml-medium-q5_0.bin539 MB

turbo is the default because it is the smallest model that still gets Romanian right. medium and below drop diacritics and mangle names, which is worse than no transcript at all: a missing transcript is a question, a wrong name is a wrong answer. large-v3 is the same accuracy for several times the wait.

An API, OpenAI-compatible

wazap config transcribe openai asks for the key without echoing it and stores it in <data-dir>/.env. The default endpoint is OpenAI; Groq works unchanged:

WAZAP_TRANSCRIBE_URL=https://api.groq.com/openai/v1
WAZAP_TRANSCRIBE_MODEL=whisper-large-v3-turbo

With this provider the audio leaves your machine. Every voice note wazap transcribes is uploaded to that endpoint. If that is not acceptable, use local, which uploads nothing.

The key is treated as a secret rather than as a setting:

  • It is never accepted as a command-line argument, because an argument lands in your shell history and in ps.
  • The prompt echoes nothing, not even asterisks.
  • It is stored only in <data-dir>/.env, mode 0600.
  • status, status --json, config and get_status show at most api key: set (…abcd).
  • A provider's own error message has the key stripped out of it before wazap prints it.
  • A plain-http WAZAP_TRANSCRIBE_URL is refused unless it points back at this machine.

Without being asked

With a provider configured, incoming voice notes of up to ten minutes are transcribed in the background as they arrive, one at a time, never holding up a message. The transcript is cached by message id and persisted, so a voice note is transcribed once and not again after a restart. Audio files are left alone, since one can be an hour long; call transcribe_audio(message_id) for those. WAZAP_TRANSCRIBE_AUTO=0 keeps the tool and stops the background work.

Skills

wazap ships five Agent Skills that teach an agent the workflows behind the tools, not just the tools:

SkillWhat the agent does
wazap-setupDiagnose with wazap status, link by QR or pairing code, connect a client with wazap connect, repair an expired session
whatsapp-inbox"What did I miss?" Triage into needs you / FYI / noise, ranked, plus forgotten replies. Read-only
whatsapp-recall"Find the invoice Dan sent." Search with query variants, page back in time, download and read the file. Read-only
whatsapp-groupsCatch up on a 300-message group: decisions, dates, what is asked of you. Read-only
whatsapp-sendDraft in the chat's own register, show recipient and text, send only after the user says yes

wazap setup copies them into every client it connects, so there is usually nothing to run. The command behind it, for a harness setup never offered or for a checkout you want to install by hand:

npx wazap-mcp skills install codex     # or claude-code, cursor, opencode, agents

With no harness named it installs into every client it finds on this machine. For Claude Code the other route is the plugin, which carries the server as well:

/plugin marketplace add razvangirgiz/wazap
/plugin install wazap@wazap

Shortened here. Read the whole README on GitHub.

Signals

GitHub stars
1
Last commit
Oct 2026
Weekly downloads
490
Weekly_downloads
490 weekly_downloads
Advanced
Delivery
wazap MCP server → your Ahel connector (mcp.ahel.ai) → your AI.
Item type
mcp-server
Key
io-github-razvangirgiz-wazap
Source
github.com/razvangirgiz/wazap