-
Notifications
You must be signed in to change notification settings - Fork 1
MCP Server
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+
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 cacheConfig (add to .claude/settings.json):
{
"mcpServers": {
"zalo": {
"command": "zalo-agent",
"args": ["mcp", "start"]
}
}
}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 ✓
Your server monitors Zalo 24/7. AI agents connect over HTTP instead of stdio.
zalo-agent mcp start --http 3847 --auth my-secret-tokenMCP clients connect via POST http://your-vps:3847/mcp with Authorization: Bearer my-secret-token.
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"
}
}Filter noise — watch only DMs, or only specific groups:
{
"watchThreads": ["dm:*"],
"triggerKeywords": ["@bot", "urgent"]
}| 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?
|
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
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_cachewith underscore). CLI commands use POSIX flags (--no-cachewith double dash). Both achieve the same result.
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_historyreads from cache instantly (no network call) -
msg searchon the CLI finds messages received during MCP session -
msg historyon the CLI reads from cache instead of hitting Zalo API
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.
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 |
# 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.jsoncurl http://localhost:3847/health
# → {"status":"ok","uptime":123,"threads":5}- 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-agentsession (no extra login)