Skip to main content
Glama
wawimundo

Viber MCP Server

by wawimundo

Viber MCP (macOS)

An MCP server that lets an AI assistant (e.g. Claude) send and read Viber messages by driving your already-running, signed-in Viber Desktop app on macOS.

Viber has no local/public API and its message database is encrypted, so this works entirely through UI automation:

  • Send — open the chat via the viber:// URL scheme, paste the text, and click the send button.

  • Receive — capture the visible Viber window and OCR it with Apple's Vision framework.

⚠️ Unofficial. Not affiliated with, endorsed by, or supported by Viber / Rakuten. It automates the desktop UI on your own machine and account. UI automation is inherently version-sensitive and may break when Viber updates. Use at your own risk and review your account's terms of service.


Why it works this way

Three hard constraints shaped the design (all verified on Viber v28.2.0):

  • The message DB is unreadable. ~/Library/Application Support/ViberPC/<number>/viber.db is encrypted with a Botan page codec (random header, not SQLite format 3), with no public key-derivation recipe. So history can't be read from disk.

  • The accessibility tree is unusable. Viber is a Qt app; AX queries hang, so there's no element tree to read or click.

  • Qt ignores ordinary synthetic input. Every click/keystroke must be generated from a real HID event source (CGEventSourceCreate(kCGEventSourceStateHIDSystemState)) — plain CGEvents and System Events keystrokes are silently dropped by Qt's input widgets.

What's left — and what this uses — is the viber:// URL scheme, HID-sourced input, and screen OCR.

Related MCP server: MCP Remote macOS Control Server

Tools

Tool

Description

send_message(to, text, dry_run=False)

Open the chat for to, confirm it, paste text, click send, and OCR-verify it landed. dry_run pastes into the box but doesn't send.

reply(text)

Send text into the currently-open chat — no navigation. Pair with read_open_chat.

open_chat(to)

Bring a conversation to the foreground without sending.

open_and_read(to)

Open a conversation and return its visible text in one step.

read_open_chat()

OCR the currently-visible Viber window and return its text.

save_to_my_notes(text)

Save a note to Viber's private "My Notes" self-chat (opens it via search, then sends).

open_my_notes()

Open the "My Notes" self-chat without saving.

list_chats()

List the sidebar chats (names, previews, times, unread counts) via OCR — a quick inbox overview.

put_draft(text)

Paste text into the currently-open chat's compose box — no navigation, no send. For drafting a reply to review.

search_and_open(name)

Search Viber by name and open the best-matching contact/chat (exact/prefix beats substring). Returns matched + low_confidence; verify before sending if low_confidence.

list_contacts()

Return the name → number aliases from contacts.json.

to is either an E.164 number (+639171234567) or a name defined in contacts.json. Unknown names error out rather than guess a recipient. send_message returns verified: true when the sent message is confirmed on screen (false = not seen, null = couldn't check).

Requirements

  • macOS (tested on macOS 15/26, Apple Silicon)

  • Viber Desktop installed, running, and signed in

  • uv (Python is managed by uv)

Install

git clone <your-fork-url> viber-mcp
cd viber-mcp
uv sync
cp contacts.example.json contacts.json   # then edit with your real contacts

macOS permissions (required)

Grant these to the app that launches the server (your terminal — iTerm/Terminal — or Claude), in System Settings → Privacy & Security:

  • Accessibility — synthetic clicks/keystrokes (sending).

  • Screen Recording — window capture (reading + send verification).

The first send/read fails with a clear message until these are granted. After granting, fully quit and reopen the host app so the permission binds.

If you get not allowed to send keystrokes (error 1002) while the toggle already looks on, reset the stuck TCC entry and relaunch: tccutil reset Accessibility <your-terminal-bundle-id> (e.g. com.googlecode.iterm2, com.apple.Terminal).

Configure contacts

contacts.json maps friendly names to E.164 numbers (it is git-ignored — it holds real numbers):

{
  "Me": "+639171234567",
  "Mom": "+639181234567"
}

Names that aren't in the file must be given as full E.164 numbers.

Use it from Claude Code

Register the server (user scope):

claude mcp add --transport stdio --scope user viber -- \
  uv --directory "$(pwd)" run server.py

Check with claude mcp list (should show viber … ✓ Connected). After editing the server code, reconnect so Claude reloads it (/mcp → reconnect viber, or restart Claude Code). Remove with claude mcp remove viber.

/viber-reply slash command (optional convenience)

Copy commands/viber-reply.md into ~/.claude/commands/ (user scope) or your project's .claude/commands/. It reads the open chat, drafts a reply in your voice, and pastes it into the compose box without sending — you review and send.

/viber-reply
/viber-reply keep it short and warm; say I'll call tonight

Use it standalone (no Claude)

A small MCP client is included for testing:

uv run client.py list                              # advertise tools
uv run client.py contacts                          # show alias map
uv run client.py open "Mom"                        # open a chat
uv run client.py send "Mom" "hello" --dry-run      # fill box, don't send
uv run client.py send "Mom" "hello"                # actually send
uv run client.py draft "a reply to review"         # drop draft in open chat (not sent)
uv run client.py read                              # OCR the open chat

How it works (details)

  • Send: open viber://chat?number=<E164> opens the chat (desktop navigation takes a few seconds, so the window is polled via OCR until the recipient's chat is confirmed — and the send is refused if the number "isn't on Viber"). Then: click the compose box, clear any draft, paste the clipboard text (emoji/unicode/newlines survive), click the send button, and OCR-verify the bubble appeared. The clipboard is saved and restored around the paste.

  • Receive: the largest on-screen window owned by "Rakuten Viber" is captured with screencapture -l <id> and OCR'd by VNRecognizeTextRequest. It only sees what's on screen.

Automation constraints

Because there's no API — only synthetic input + OCR against a Qt app — a few non-obvious rules make or break reliability. Keep these in mind when running the tools or editing viber_send.py:

  • Synthetic input must use a real HID event source. Qt silently drops plain CGEvents and System Events keystrokes; the code posts every click/keystroke from CGEventSourceCreate(kCGEventSourceStateHIDSystemState).

  • Sidebar, search box, send button, and panel-close ✕ are targeted by fixed PIXEL offsets from the window edges — not fractions. The sidebar and side panels have fixed pixel widths, so fraction-based clicks miss on wide/maximized windows.

  • Any right-side panel (chat-info or sticker "Settings") must be closed before sending — it shifts the send button. The code detects and closes it automatically.

  • Sending clicks the send button; it does not press Return ("Send message on Enter" may be off).

  • viber:// chat navigation is slow and mobile-first — the code polls via OCR until the target chat is confirmed, and refuses to send if the number "isn't on Viber". You cannot message your own number ("cannot add yourself"); My Notes is a separate chat.

  • Keep Viber in a normal window on the main desktop — not macOS fullscreen. Fullscreen puts it on a separate Space, causing window-ID churn and blank captures. Don't resize it mid-operation, and prefer keeping side panels closed.

  • OCR sees only on-screen text and can garble small/emoji text or interleave the sidebar with the open conversation; the returned verified flag is best-effort (false negatives are common).

Project layout

File

Purpose

server.py

FastMCP stdio server; defines the tools

viber_send.py

platform-neutral recipient resolution, send pipeline, search scoring

viber_ocr.py

platform-neutral formatting of OCR observations into read results

backends/base.py

Backend interface (semantic: observe, clicks, keys, clipboard) + OCRError

backends/macos.py

macOS backend — the only file with OS APIs (Quartz/Vision/AppKit) + Viber layout

backends/__init__.py

get_backend() selects a backend by platform

client.py

standalone MCP client for testing

contacts.json

your name → number map (git-ignored)

contacts.example.json

template to copy

commands/viber-reply.md

optional Claude Code slash command

Porting: add backends/<os>.py implementing Backend, wire it into get_backend(), and re-tune that platform's UI-element locations. All domain logic and tools are shared. (Windows can likely target elements via UI Automation instead of coordinates; Linux/Wayland restricts capture and synthetic input, so X11 is easier.)

Limitations & caveats

  • OCR sees only on-screen text — scroll to bring older messages into view; it can garble text or interleave the sidebar's chat previews with the conversation.

  • No message history from disk (encrypted DB).

  • Names resolve from contacts.json — Viber's own contact list can't be read.

  • Version-sensitive: a Viber UI/layout change can require small coordinate tweaks.

  • Messaging your own number is blocked by Viber ("cannot add yourself"); "My Notes" is separate.

Privacy & security

  • Runs entirely locally; it does not send your messages or contacts anywhere except into Viber.

  • contacts.json is git-ignored so real numbers don't get committed.

  • Requires powerful macOS permissions (Accessibility, Screen Recording) for the host app — grant only if you understand and trust that.

License

MIT — see LICENSE.

A
license - permissive license
-
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/wawimundo/viber-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server