wechat-mcp

wechat-mcp

An MCP server for sending and receiving WeChat messages, built on Tencent's iLink bot protocol.

Category
Visit Server

README

wechat-mcp

A Model Context Protocol (MCP) server for sending and receiving WeChat (Weixin) messages, built on Tencent's iLink bot protocol.

This is a port of the messaging core of Tencent/openclaw-weixin (an OpenClaw channel plugin) into a standalone MCP server usable from Claude Code, Claude Desktop, or any MCP client. The WeChat protocol logic — QR login, the getUpdates long-poll receive loop, sendMessage, and the AES-128-ECB CDN media pipeline — is preserved; the OpenClaw runtime/SDK coupling has been removed and replaced with a thin MCP tool layer.

What it does

MCP tool Purpose
wechat_login QR-code login. Prints a QR to the terminal (STDERR); scan with WeChat mobile and confirm. Persists the bot token.
wechat_list_accounts List logged-in WeChat bot accounts.
wechat_logout Remove an account's stored credentials, sync cursor, and context tokens.
wechat_send Send text and/or a media attachment (image / video / file) to a user. Accepts a local path or a remote http(s) URL. Text is markdown-filtered by default (WeChat-unsupported syntax stripped); pass filterMarkdown: false for raw text.
wechat_receive Poll for new inbound messages (one long-poll cycle). Tracks a per-account sync cursor so repeated calls don't return duplicates. Inbound media is downloaded + decrypted to local temp files.
wechat_listen Continuously poll until a message arrives, an error occurs, or the window elapses (default 2 min). Re-polls back-to-back — the reliable way to wait for a message, since a single wechat_receive cycle often returns empty early.
wechat_typing Show (or cancel) the "typing…" indicator for a user. The typing ticket is resolved automatically.

Requirements

  • Node.js >= 20
  • A WeChat (Weixin) mobile app to scan the login QR code

Install

Clone and install. The prepare hook compiles TypeScript to dist/ automatically on npm install, so there is no separate build step.

git clone https://github.com/jimingyuan7/wechat-mcp.git wechat-mcp
cd wechat-mcp
npm install

To rebuild after editing source: npm run build.

Optional: voice-message transcoding (SILK → WAV) requires the optional silk-wasm package. Without it, inbound voice is saved as raw .silk.

npm install silk-wasm

First-time login

Run the interactive login in a real terminal (the QR renders to STDERR):

npm run login

Scan the QR code with the WeChat mobile app and confirm. Credentials are saved under ~/.wechat-mcp/openclaw-weixin/accounts/.

You can also trigger login through the wechat_login MCP tool, but a real terminal is friendlier for scanning the QR.

Register with an MCP client

Claude Code

Prerequisites: the project is built (npm install already ran the prepare build, so dist/mcp/server.js exists) and you have logged in once (npm run login).

1. Add the server. Run this from anywhere — use the absolute path to the built entry point:

claude mcp add wechat -- node /absolute/path/to/wechat-mcp/dist/mcp/server.js

Tip: if you're inside the project dir, $(pwd) fills the path in for you:

claude mcp add wechat -- node "$(pwd)/dist/mcp/server.js"

By default the server is added at local scope (only this project on this machine). To make it available across all your projects, use user scope:

claude mcp add -s user wechat -- node /absolute/path/to/wechat-mcp/dist/mcp/server.js

To override a config env var (e.g. a custom state dir), pass -e:

claude mcp add wechat \
  -e WECHAT_MCP_STATE_DIR=/data/wechat \
  -- node /absolute/path/to/wechat-mcp/dist/mcp/server.js

2. Verify it's connected:

claude mcp list          # should show: wechat ✓ connected
claude mcp get wechat    # shows the full command + health

3. Use it. Start claude, and the 7 wechat_* tools are available. Just ask in natural language, e.g.:

"Use wechat_listen to wait for a WeChat message, then reply with a friendly greeting."

Remove when you no longer need it:

claude mcp remove wechat

Note: first-time WeChat login (npm run login) needs a real terminal to scan the QR code, so do that before relying on the tools inside Claude Code. Credentials persist under ~/.wechat-mcp/, so you only log in once.

Claude Desktop (claude_desktop_config.json)

{
  "mcpServers": {
    "wechat": {
      "command": "node",
      "args": ["/absolute/path/to/wechat-mcp/dist/mcp/server.js"]
    }
  }
}

Usage notes

  • Recipient ids look like xxxxxxxx@im.wechat. You normally obtain one from an inbound message (wechat_receive → message From).
  • Context tokens: the WeChat backend issues a per-conversation context_token on each inbound message that must be echoed on outbound sends. The server caches these automatically (in memory + on disk) as messages arrive, so wechat_send to a user who has recently messaged the bot "just works". Sending to a user with no cached token may be rejected by the backend.
  • Receiving is poll-based: call wechat_receive repeatedly (e.g. in a loop). Each call holds the connection open up to timeoutMs (default 35s) waiting for new messages, then returns. The sync cursor is persisted, so you never see the same message twice across calls or restarts.
  • Media: outbound media is auto-classified by file extension (video/*, image/*, else generic file). Inbound media is downloaded, AES-128-ECB decrypted, and written to ~/.wechat-mcp/tmp/media/inbound/; the local path comes back in the message's MediaPath.

Usage examples

In a chat with an MCP client (e.g. Claude Code / Claude Desktop) you just ask in natural language — "reply to the last WeChat message", "send this photo to the user", etc. The tool-call payloads below show what the client sends under the hood, and are also handy for direct/manual testing.

The recommended flow is receive first, then reply: an inbound message caches the context_token that outbound sends require.

1. See who's logged in

{ "name": "wechat_list_accounts", "arguments": {} }
// → result
{ "count": 1, "accounts": [
  { "accountId": "bfa52ff0d915-im-bot",
    "userId": "o9cq...@im.wechat", "configured": true }
] }

2. Wait for an incoming message (recommended over wechat_receive)

wechat_listen re-polls until a message arrives or the window elapses:

{ "name": "wechat_listen", "arguments": { "windowMs": 120000 } }
// → result (returns as soon as a message arrives)
{ "messages": [
  { "From": "o9cq...@im.wechat", "Body": "Hello",
    "MediaPath": null, "context_token": "AARz..." }
], "pollCycles": 3, "timedOut": false }

Copy From — that's the to you reply to. The context_token is now cached, so the next send will actually deliver.

3. Reply with text

{ "name": "wechat_send", "arguments": {
  "to": "o9cq...@im.wechat", "text": "Hi! Got it 👍" } }
// → result
{ "messageId": "wechat-mcp:...", "hadContextToken": true, "markdownFiltered": false }

hadContextToken: true means it will be delivered. If it's false, the recipient hasn't messaged the bot yet — have them send one message first.

4. "Typing…" indicator before a slow reply

{ "name": "wechat_typing", "arguments": { "to": "o9cq...@im.wechat" } }

…do your slow work (call an LLM, fetch data), then wechat_send the result. Cancel the indicator early with { "to": "...", "status": "cancel" }.

5. Send an image or file

Local path (absolute recommended) or a remote URL — type is auto-detected:

{ "name": "wechat_send", "arguments": {
  "to": "o9cq...@im.wechat", "text": "Here's the photo", "media": "/tmp/photo.png" } }
{ "name": "wechat_send", "arguments": {
  "to": "o9cq...@im.wechat", "media": "https://example.com/cat.jpg" } }

6. Markdown handling

Outbound text is markdown-filtered by default — WeChat-unsupported syntax (H5/H6 headings, CJK italics *…*, inline images) is stripped so users see clean text instead of stray symbols. Pass filterMarkdown: false to send raw:

{ "name": "wechat_send", "arguments": {
  "to": "o9cq...@im.wechat", "text": "raw **markdown** stays", "filterMarkdown": false } }

Note: WeChat chat bubbles do not render rich text at all — filtering only removes noisy markers; it cannot make text bold/italic on the WeChat side.

Quick CLI smoke test (no MCP client)

You can drive the server over stdio directly:

printf '%s\n' \
  '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"cli","version":"0"}}}' \
  '{"jsonrpc":"2.0","method":"notifications/initialized"}' \
  '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"wechat_list_accounts","arguments":{}}}' \
  | node dist/mcp/server.js

Configuration (environment variables)

Variable Default Description
WECHAT_MCP_STATE_DIR ~/.wechat-mcp Where credentials, sync cursors, and context tokens are stored.
WECHAT_MCP_TMP_DIR <state>/tmp Temp dir for downloaded / decrypted media.
WECHAT_MCP_LOG_LEVEL INFO TRACE DEBUG INFO WARN ERROR. Logs go to STDERR.
WECHAT_MCP_BOT_AGENT WeChatMCP UA-style self-identifier sent on every request (for backend log attribution).
WECHAT_MCP_CDN_BASE_URL Tencent C2C CDN Override the media CDN base.
WECHAT_MCP_ROUTE_TAG Optional SKRouteTag header.

Architecture

src/
  api/         iLink HTTP+JSON protocol (getUpdates, sendMessage, getUploadUrl, …) + types
  auth/        QR login flow + per-account credential store
  cdn/         AES-128-ECB encrypt/decrypt + CDN upload/download
  media/       MIME mapping, media download/decrypt, optional SILK→WAV transcode
  messaging/   send (text/image/video/file), inbound normalization + context tokens,
               receive (single cycle + receiveUntil listen loop), outbound (high-level
               send w/ markdown filter), typing (indicator), markdown-filter
  storage/     state-dir resolution + sync-buf (getUpdates cursor) persistence
  util/        logger (STDERR-only), redaction, id/account-id helpers
  mcp/         MCP stdio server exposing the 5 tools

The STDOUT stream is reserved exclusively for the MCP JSON-RPC protocol; all human-facing output (logs, QR codes, prompts) goes to STDERR.

Credits

Protocol implementation ported from Tencent/openclaw-weixin (MIT).

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured