Skip to content

MCP Server

ardennguyen edited this page Jun 30, 2026 · 3 revisions

MCP Server (AI Agent Integration)

Connect Claude Code (or any MCP client) to your Zalo conversations. The MCP server exposes 7 tools that let AI agents read, reason about, and reply to Zalo messages in real-time.

Added in v1.0.0 · Local cache added in v1.1.0 · Requires Node.js 20+


User Stories

🤖 "I want Claude Code to read my Zalo messages"

You're working in the terminal and want Claude to check if anyone messaged you on Zalo — without switching apps.

# 1. Start MCP server (background process)
zalo-agent mcp start

# 2. Claude Code auto-discovers the tools and can call:
#    zalo_get_messages → read buffered messages
#    zalo_list_threads → see active conversations
#    zalo_get_history  → read historical messages from cache

Config (add to .claude/settings.json):

{
  "mcpServers": {
    "zalo": {
      "command": "zalo-agent",
      "args": ["mcp", "start"]
    }
  }
}

💬 "I want Claude to reply on my behalf"

You're in a coding session and a colleague asks a quick question on Zalo. Tell Claude: "reply to that message with ..." and it calls zalo_send_message.

You: "Reply to thread X saying I'll check it after lunch"
Claude: [calls zalo_send_message with threadId, text, threadType]
→ Message sent ✓

📡 "I want to run MCP on my VPS"

Your server monitors Zalo 24/7. AI agents connect over HTTP instead of stdio.

zalo-agent mcp start --http 3847 --auth my-secret-token

MCP clients connect via POST http://your-vps:3847/mcp with Authorization: Bearer my-secret-token.

🔔 "I want Zalo group notifications when agent is offline"

When no AI agent is connected, forward message summaries to a Zalo group so you don't miss anything.

Edit ~/.zalo-agent-cli/mcp-config.json:

{
  "notify": {
    "enabled": true,
    "thread": "GROUP_ID_HERE",
    "on": ["dm"],
    "cooldown": "5m"
  }
}

🧹 "I only care about certain conversations"

Filter noise — watch only DMs, or only specific groups:

{
  "watchThreads": ["dm:*"],
  "triggerKeywords": ["@bot", "urgent"]
}

Tools Reference (7 tools)

Tool Description Key Params
zalo_get_messages Get buffered messages (cursor-based polling) threadId?, since, limit
zalo_send_message Send text to a thread threadId, text, threadType (0=DM, 1=Group)
zalo_list_threads List active threads with unread counts type (dm/group/all)
zalo_search_threads Search threads by name (fuzzy, accent-insensitive) query, type, limit
zalo_mark_read Discard messages up to cursor cursor
zalo_get_history Message history — cache-first (v1.1.0) threadId, limit, threadType, no_cache
zalo_view_media Open media attachment with system viewer messageId, threadId?, open?

Cursor-based Polling

1st call:  zalo_get_messages()           → { messages: [...], cursor: 5, hasMore: false }
2nd call:  zalo_get_messages(since: 5)   → only new messages after cursor 5
Cleanup:   zalo_mark_read(cursor: 5)     → discard old messages from buffer

zalo_get_history — Cache-First (v1.1.0)

By default, zalo_get_history reads from the local cache (instant, no network).

// Cache-first (default) — reads from zalo.db, returns source: "cache"
{ "threadId": "uid123", "limit": 50 }

// Force live fetch from Zalo + backfill cache — returns source: "live"
{ "threadId": "uid123", "limit": 50, "no_cache": true }

Note on naming: MCP tool parameters use JSON keys (no_cache with underscore). CLI commands use POSIX flags (--no-cache with double dash). Both achieve the same result.


Local Cache (v1.1.0)

mcp start passively writes every incoming message to zalo.db — in addition to the in-memory buffer.

zalo-agent mcp start
  → [mcp] Local cache active — events will be persisted to zalo.db

Cache path: ~/.zalo-agent-cli/accounts/<ownId>/zalo.db

This means:

  • zalo_get_history reads from cache instantly (no network call)
  • msg search on the CLI finds messages received during MCP session
  • msg history on the CLI reads from cache instead of hitting Zalo API

Architecture

Zalo Cloud ──WebSocket──→ zalo-agent mcp start
                               │
                   ┌───────────┼──────────────┐
                   │           │              │
             SQLite zalo.db  Thread       Notifier
             (ALL messages)  Filter      (Zalo group)
             passive write   glob        batched alerts
              v1.1.0          noise       cooldown window
                   │           │
                Ring Buffer (filtered subset)
                cursor-based, auto-evict
                   │
              MCP Server (stdio or HTTP)
                   │
             Claude Code / MCP Client

SQLite (v1.1.0): Every message written to per-account zalo.db for offline search and cache-first history.

Ring Buffer: Stores filtered messages per thread. Auto-evicts by age (default 2h) and size (default 500).

Thread Filter: Glob patterns (dm:*, group:support_123) control which threads feed the buffer. Noise filter drops stickers, system messages, and short emoji-only messages.

Notifier: When no agent is connected, batches unread DM notifications and sends a summary to a configured Zalo group.


Configuration

Config file: ~/.zalo-agent-cli/mcp-config.json

Field Default Description
watchThreads ["dm:*", "group:*"] Glob patterns for threads to monitor
mode "manual" Polling mode
triggerKeywords ["@bot"] Keywords that trigger attention
notify.enabled false Enable group notifications
notify.thread null Group ID to send notifications to
notify.on ["dm"] Event types to notify on
notify.cooldown "5m" Batch window before sending
limits.maxMessagesPerPoll 20 Max messages per get_messages call
limits.bufferMaxAge "2h" Auto-evict messages older than this
limits.bufferMaxSize 500 Max messages per thread

Commands

# Local (stdio — for Claude Code)
zalo-agent mcp start

# HTTP (for VPS / remote agents)
zalo-agent mcp start --http 3847

# HTTP with auth
zalo-agent mcp start --http 3847 --auth your-secret-token

# Custom config
zalo-agent mcp start --config /path/to/mcp-config.json

Health Check (HTTP mode)

curl http://localhost:3847/health
# → {"status":"ok","uptime":123,"threads":5}

Security

  • stdio: No auth needed (local process, same user)
  • HTTP: Bearer token auth on all endpoints except /health
  • Reconnect: Auto re-login on WebSocket close (except duplicate session = fatal)
  • Credentials: Reuses existing zalo-agent session (no extra login)

Clone this wiki locally