Nivox MCP — connect any AI agent to your WhatsApp Business

Nivox exposes a WhatsApp Business account as a Model Context Protocol (MCP) server. Any MCP client — claude.ai, Claude Desktop, ChatGPT, Claude Code, Codex, Cursor, Windsurf, VS Code, Gemini CLI, custom agents — can read the inbox, answer customers, send templates and manage templates with the same rules the human team follows.

MCP endpoint https://nivox.me/mcp
Transport Streamable HTTP (JSON responses, stateless — no SSE stream, no session id)
Protocol versions 2025-06-18, 2025-03-26, 2024-11-05
Authentication OAuth 2.1 (recommended, "Authorize" button) or API key (Authorization: Bearer nvx_…)
Machine-readable copy of this page https://nivox.me/docs/mcp.md

If you are an AI assistant reading this to configure a client: follow the section for the client you are running in, prefer OAuth when the client supports it, verify with list_connections, then install the skills listed at the end. Never send a WhatsApp message unless the user explicitly asks for that send.

1. Authentication

OAuth 2.1 (no key to paste)

Clients that support OAuth discover everything automatically:

  1. The client calls https://nivox.me/mcp and receives 401 with WWW-Authenticate: Bearer resource_metadata="https://nivox.me/.well-known/oauth-protected-resource/mcp".
  2. It reads https://nivox.me/.well-known/oauth-authorization-server (RFC 8414), registers itself (Dynamic Client Registration, RFC 7591) and opens the browser at https://nivox.me/oauth/authorize.
  3. The Nivox admin signs in (if needed), picks the access level and which WhatsApp numbers the client may use, and clicks Authorize.
  4. The client exchanges the code (PKCE S256) at https://nivox.me/oauth/token and receives an access token — a Nivox API key. It does not expire; the admin revokes it any time on Integrations (or the client calls https://nivox.me/oauth/revoke).

Only organization admins can authorize. Metadata endpoints:

API key

An admin generates a key on Integrations (https://nivox.me/app/integrations). The key looks like nvx_ + 40 hex characters and is shown once. Send it as Authorization: Bearer nvx_… (or x-api-key: nvx_… if the client cannot set Authorization). Keep it in the client's secret/env store, never in a committed file.

2. Client setup

claude.ai and Claude Desktop (custom connector, OAuth)

  1. Settings → Connectors → Add custom connector.
  2. Name: Nivox. Remote MCP server URL: https://nivox.me/mcp. Leave OAuth client id/secret empty (Nivox supports dynamic registration).
  3. Click Add, then Connect — the Nivox authorization screen opens; sign in as an admin and click Authorize.
  4. In a chat, enable the Nivox connector and ask, e.g. "list my WhatsApp conversations".

The connector appears in Claude Desktop automatically (same account). Custom connectors require a paid Claude plan (Pro, Max, Team or Enterprise).

ChatGPT (developer mode connector, OAuth)

  1. Settings → Connectors → Advanced → Developer mode (enable).
  2. Create a connector: Name Nivox, MCP Server URL https://nivox.me/mcp, Authentication OAuth.
  3. Save, then Connect — authorize on the Nivox screen.
  4. In a chat, add the Nivox connector from the tools menu ("+" → connectors).

Claude Code

OAuth (recommended):

claude mcp add --transport http nivox https://nivox.me/mcp
# then, inside Claude Code, run /mcp and choose "Authenticate" for nivox

API key alternative:

claude mcp add --transport http nivox https://nivox.me/mcp \
  --header "Authorization: Bearer nvx_YOUR_KEY"

Project-scoped .mcp.json (commit it without the key):

{ "mcpServers": { "nivox": { "type": "http", "url": "https://nivox.me/mcp" } } }

Codex CLI (OpenAI)

OAuth:

codex mcp add nivox --url https://nivox.me/mcp
codex mcp login nivox

API key in ~/.codex/config.toml (keep the key in an environment variable):

[mcp_servers.nivox]
url = "https://nivox.me/mcp"
bearer_token_env_var = "NIVOX_API_KEY"

Cursor

~/.cursor/mcp.json (or the project's .cursor/mcp.json), then accept the Authenticate prompt Cursor shows for the server:

{ "mcpServers": { "nivox": { "url": "https://nivox.me/mcp" } } }

With a key instead of OAuth: add "headers": { "Authorization": "Bearer nvx_YOUR_KEY" }.

Windsurf

~/.codeium/windsurf/mcp_config.json:

{ "mcpServers": { "nivox": { "serverUrl": "https://nivox.me/mcp" } } }

VS Code (GitHub Copilot agent mode)

.vscode/mcp.json; VS Code asks to sign in (OAuth) when the server starts:

{ "servers": { "nivox": { "type": "http", "url": "https://nivox.me/mcp" } } }

Gemini CLI

~/.gemini/settings.json:

{ "mcpServers": { "nivox": { "httpUrl": "https://nivox.me/mcp", "oauth": { "enabled": true } } } }

Any other MCP client

Generic Streamable HTTP configuration:

{ "mcpServers": { "nivox": { "type": "http", "url": "https://nivox.me/mcp",
  "headers": { "Authorization": "Bearer nvx_YOUR_KEY" } } } }

Clients that only speak stdio can bridge with npx -y mcp-remote https://nivox.me/mcp (OAuth) or npx -y mcp-remote https://nivox.me/mcp --header "Authorization: Bearer nvx_YOUR_KEY".

Verify

export NIVOX_API_KEY="nvx_..."   # the key from Integrations (skip with OAuth clients)
curl -sS https://nivox.me/mcp \
  -H "Authorization: Bearer $NIVOX_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Then call list_connections from the client: it must return at least one connection with status: "active".

3. Access levels (scopes)

Scope Acts as Can
read viewer list connections/conversations/templates, read history, search, download media
messages agent read + send_text, send_media, send_buttons, send_interactive, send_reaction, send_template to existing contacts, mark_read
full manager messages + send_template to new numbers, create_template, delete_template, sync_templates, get_connection_health

Tools above the granted scope are not listed; calling one returns an isError result with code: "forbidden". A grant or key may also be limited to specific WhatsApp numbers. User and key management are never reachable over MCP.

4. Tools

All ids are UUIDs returned by earlier tools. connection_id may be omitted when the credential sees a single connection. Results carry structuredContent and the same JSON as text.

Tool Scope Arguments (required) Notes
list_connections read numbers, status, quality, sync stamps
list_conversations read connection_id, unread_only, open_window_only, query, limit ≤100, before newest first; window.open = free text allowed
get_conversation read conversation_id
find_contact read phone, connection_id digits with country code; Brazilian 9th-digit variants handled
list_messages read conversation_id, limit ≤100, before, after chronological within the page; cursors next_before / next_after
search_messages read query, connection_id, conversation_id, limit ≤50 case-insensitive substring
get_media_url read message_id 1-hour signed download URL
list_templates read connection_id, status (APPROVED default / all), include_components header/body text and {{n}} counts
send_text messages conversation_id, text, reply_to_wamid, preview_url 24h window must be open
send_template messages template_name, conversation_id or phone (+connection_id), language, body_params[], header_param the only way outside the window; new phone → full
send_media messages conversation_id, media_type, url (https), caption, filename, mime, reply_to_wamid limits: image 10 MB, audio 20 MB, video/document 25 MB, sticker 2 MB
send_buttons messages conversation_id, body, buttons[{id,title}] ≤3, header, footer, reply_to_wamid quick-reply buttons
send_interactive messages conversation_id, interactive (raw Meta object), reply_to_wamid lists, cta_url, flows
send_reaction messages conversation_id, target_wamid, emoji ("" removes)
mark_read messages conversation_id, typing unread = 0 + read receipt
sync_templates full connection_id refresh the template cache from Meta
create_template full name, language, category, body_text, body_examples[], header_text, footer_text text templates, Meta review
delete_template full name, connection_id irreversible
get_connection_health full connection_id daily Meta health snapshot

Prompts offered by the server: attend_inbox, reply_to_customer, daily_summary. The initialize response also carries instructions — the operating guide for the granted scope.

Error results always include code: invalid_arguments, forbidden, not_found, session_expired (24h window closed → use send_template), account_inactive, template_not_approved, unsupported_template, too_large, fetch_failed, graph_error (Meta refused; read details), internal_error.

5. Operating rules (what a good agent does)

6. Skills (Agent Skills, SKILL.md)

Three skills teach agents the Nivox workflow. Install them into your agent's skills directory — ~/.claude/skills/<name>/SKILL.md for Claude Code, ~/.codex/skills/<name>/SKILL.md for Codex, .agents/skills/<name>/SKILL.md or the client's equivalent elsewhere:

One-liner (Claude Code; replace the directory for other agents):

for s in nivox-whatsapp-attendant nivox-whatsapp-templates nivox-mcp-setup; do
  mkdir -p ~/.claude/skills/$s && curl -fsSL https://nivox.me/skills/$s/SKILL.md -o ~/.claude/skills/$s/SKILL.md
done

The Integrations page also offers all three as a single zip download.

7. Troubleshooting

Symptom Cause / fix
401 unauthorized Missing, revoked or malformed credential. Re-authorize (OAuth) or generate a new key.
The client does not start the OAuth flow It may not support OAuth for remote MCP servers; use an API key (headers) or bridge with mcp-remote.
"Only administrators can authorize" Sign in with an admin of the organization, or switch organization on the consent screen.
Tool result code: "forbidden" Scope too low for that tool — a new grant/key with a higher level.
code: "not_found" for an id you just listed The credential is limited to other connections, or the id belongs to another organization.
code: "session_expired" 24h window closed → send_template.
code: "graph_error" Meta refused; details.code/details.message explain (template parameters, number not on WhatsApp…).
400 unsupported MCP protocol version Supported: 2025-06-18, 2025-03-26, 2024-11-05.
405 on GET Expected: the server is stateless (no SSE stream); POST JSON-RPC only.

Security: one credential per agent, least scope that works, revoke on Integrations when an agent is retired (immediate). Every message an agent sends is stored in the team inbox and attributed to the credential that sent it.