WeCom MCP Server
This MCP server lets AI agents send WeCom app messages, resolve recipients, and verify configuration through the exposed tools.
Send text messages to users, departments, or tags, with multiple IDs separated by
|and optionalagent_idoverride.Send Markdown messages to WeCom clients.
Send arbitrary message types via
msg_type+content_json(e.g., text, markdown, textcard, news, image, mpnews, template_card).Upload and send a local file (≤20 MB).
Mass-send a message to all members in the app’s visible scope via
@all.Look up a member’s UserID by name, using exact match first then fuzzy matching; returns all same-name matches.
List department members (name + UserID), optionally recursing into child departments.
Check whether the current WeCom configuration is ready without making a network request.
Provides tools for interacting with WeChat Work (WeCom) self-built applications, enabling AI agents to send messages (text, Markdown, files), query contacts, receive and store messages, and search historical messages via WeCom APIs and callbacks.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@WeCom MCP Server Send a text message to zhangsan: 'Please check the report.'"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
WeCom MCP Server

A Python-based MCP (Model Context Protocol) Server for WeCom (WeChat Work / 企业微信) self-built applications.
Provides a standard set of MCP tools that enable AI Agents (e.g., Trae Work, Claude Desktop) to send messages to WeCom users/departments, query contacts, receive and store messages, and more.
✨ Features
Core Capabilities
📨 Message Sending: Text, Markdown, generic message bodies, file sending, and
@allmass messaging📇 Contact Lookup: Quickly find UserIDs by member name, or batch-retrieve members by department
📥 Message Reception: In HTTP mode, automatically listens for WeCom callbacks and persists received messages to SQLite in real time
🔍 History Query: Query historical messages by time, type, sender, keywords, and more
🗄️ File Management: Received images/voice/video/files are automatically downloaded to local disk with metadata stored in the database
✅ Approval Integration: Connects to the "Workbench - Approval" open APIs to export approval submission records by time / template / applicant / status, with built-in statistics
Technical Highlights
Pure-Python AES-256-CBC: No dependency on the
cryptographylibrary, avoiding native compilation issues on certain platformsMultiple Transport Support:
stdio(single Agent),streamable-http(shared by multiple Agents),sse(legacy compatibility)SQLite Yearly Partitioning: Automatically splits data tables by calendar year to prevent single-table bloat
Dependency-Free XLSX Writer: Built-in OOXML writer produces real Excel workbooks (multi-sheet, frozen header, auto-filter) even in offline environments
Managed by
uv: Ultra-fast package manager and virtual environment
Related MCP server: WeCom MCP
🛠️ MCP Tool List
Universal Tools (Stdio & HTTP/SSE)
Tool Name | Description |
| Send a text message |
| Send a Markdown message |
| Send any message type; with |
| Find UserID by member name |
| List all members in a department (including sub-departments) |
Approval Tools (Universal — Stdio & HTTP/SSE)
Backed by the "Workbench - Approval" open APIs. The accessible data range is determined by both the "Approval Data Permission" grant and the self-built app's visible scope.
Tool Name | Description |
| Export approval submission records by filters (CSV / XLSX / JSON) with statistics |
| Get one approval record (form content, approval flow, comments) |
| List approval templates and their control schema |
| Search form content across exported records; add |
department_idfilters by department subtree: the WeCom server-sidedepartmentfilter already includes child departments, and the local second-pass filter matches the same way, so passing a parent department never drops its children's records.
HTTP/SSE Exclusive Tools
Tool Name | Description |
| Health check (Webhook status, database status, etc.); add |
| Query historical messages (filter by time/type/sender/keywords) |
| Get details of a single message; add |
| Historical message statistics (by type/sender/month); add |
🚀 Quick Start
1. Requirements
Python >= 3.14
uv package manager
2. Installation & Configuration
# Clone the project
git clone <your-repo-url>
cd wecom-mcp-server
# Create virtual environment and install dependencies
uv sync
# Copy the environment variable template
cp .env.example .env3. Configuration
Edit .env and fill in your WeCom self-built application credentials:
# Corp ID
WECOM_CORP_ID=ww1234567890abcdef
# Self-built App Secret
WECOM_CORP_SECRET=your_app_secret
# Self-built App AgentId (number)
WECOM_AGENT_ID=1000002
# ====== Optional ======
# Contacts Sync Secret (recommended for full-directory access)
WECOM_CONTACTS_SECRET=your_contacts_secret
# Message callback (only needed for receiving messages / history query)
WECOM_CALLBACK_TOKEN=your_callback_token
WECOM_CALLBACK_ENCODING_AES_KEY=your_aes_key_43_chars_long
# MCP service mode
# stdio: default, single-client local calls
# streamable-http: HTTP service, shared by multiple Agents
# sse: legacy SSE mode
WECOM_MCP_TRANSPORT=streamable-http
WECOM_MCP_HOST=0.0.0.0
WECOM_MCP_PORT=8101
# Data persistence directory
WECOM_DATA_DIR=data
# Concurrency for approval detail fetching (WeCom API limit: 600 requests/minute)
WECOM_APPROVAL_CONCURRENCY=84. Run
Mode 1: Stdio (for Trae Work)
Add to your Trae Work MCP configuration:
{
"mcpServers": {
"wecom": {
"command": "uv",
"args": ["run", "--directory", "/path/to/wecom-mcp-server", "wecom-mcp-server"]
}
}
}Mode 2: HTTP Service (shared by multiple Agents)
# Run in foreground
uv run wecom-mcp-server --transport streamable-http --host 0.0.0.0 --port 8101
# Or use .env configuration
WECOM_MCP_TRANSPORT=streamable-http WECOM_MCP_PORT=8101 uv run wecom-mcp-serverCallers interact with the MCP Server via HTTP:
curl -X POST http://localhost:8101/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "send_text_message", "arguments": {"to_user": "zhangsan", "content": "Hello!"}}}'✅ Approval Feature (Workbench - Approval)
Prerequisites (WeCom Admin Console)
Three grants are required:
App Management → Approval → API → Callable Apps: add the self-built app used by this service;
App Management → Approval → API → Approval Data Permission: authorize the app to read approval data;
Self-built app visible scope: must cover the target applicants — records outside the scope are filtered server-side.
Also add the machine's egress IP under App Management → Self-built App → Enterprise Trusted IP,
otherwise the API returns errcode=60020.
Without these grants the API returns errcode=301055 / 60020; the tool surfaces the matching guidance directly.
Name & Department Resolution
The approval detail API returns only userid — never names. Names are resolved through the
contacts API, with the following fallback chain:
Priority | Source | Coverage | Requirement |
1 |
| Whole company | Management Tools → Contact Sync → enable "API Interface Sync" |
2 | The app's own secret (automatic fallback) | Members within the app's visible scope | No extra setup |
If WECOM_CONTACTS_SECRET is set but "API Interface Sync" was never enabled, the API returns
48009 and the service transparently falls back to the app secret — names and departments
still resolve as long as the applicants are inside the app's visible scope.
Note:
department_id=0(whole company) is supported only by the contact-sync secret; with the app secret the server replies60003. The service degrades automatically to "fetch the department list, then pull members per top-level department" — no manual step needed.
Typical Calls
// 1) Export last month's reimbursements, filtered by template / applicant / status
{
"name": "export_approval_records",
"arguments": {
"start_date": "2026-09-01",
"end_date": "2026-09-30",
"template_name": "报销",
"sp_status": "审批中,已通过",
"output_format": "xlsx",
"output_path": "data/exports/2026-09-reimbursements.xlsx"
}
}
// 2) Group statistics over exported data (pass rate, duration, amounts)
{ "name": "search_approval_records", "arguments": { "start_date": "2026-09-01", "with_stats": true } }
// 3) Search approvals whose form content contains a keyword
{ "name": "search_approval_records", "arguments": { "keyword": "travel" } }Exported Columns
Column | Description |
Approval No. / Template Name / Template ID | Record identity and owning template |
Status | In approval / Approved / Rejected / Withdrawn / Withdrawn after approval / Deleted / Paid |
Submitted At / Completed At / Duration (h) | Completion time = last action timestamp across approval nodes |
Applicant / UserID / Department | Names resolved from the contacts API (subject to visible scope) |
Amount Total | Sum of top-level money controls (nested detail amounts are reported separately to avoid double counting) |
Form Content | Every control rendered as readable text, with detail-table rows expanded |
Approval Flow | Node index, approval mode (single / countersign), approver, comment and timestamp |
Comments / Node Count / Attachment Count / Notified | Supplementary approval metadata |
XLSX output contains multiple sheets: 审批明细 / 按模板统计 / 按状态统计 / 按申请人统计 / 金额汇总.
Analysis Dimensions
Status distribution, per-template breakdown (pass rate / average duration / amount), top applicants, submissions per day and per month, approval duration (mean / median / max / min), per-field amount aggregation, plus derived conclusions (overall pass rate, lowest pass-rate template, slowest template, overdue pending records).
API Limits (handled automatically in the service layer)
Limit | Handling |
| Split into 31-day windows and fetch each |
100 records per page | Automatic cursor pagination |
600 requests/minute | Detail fetch concurrency tuned by |
Global per-company concurrency cap on the same API ( | Concurrency stays low; on |
A single failing detail | Logged and skipped; the rest of the export still succeeds |
⚙️ Supervisor Deployment
The service is managed by the system-wide supervisord, with its config at
/usr/local/etc/supervisor.d/wecom-mcp-server.conf.
[program:wecom-mcp-server]
command=/path/to/wecom-mcp-server/scripts/run_mcp_server.sh
directory=/path/to/wecom-mcp-server
user=your_username
autostart=true
autorestart=true
startretries=3
stopasgroup=true
killasgroup=true
stderr_logfile=/path/to/logs/wecom-mcp-server.err.log
stdout_logfile=/path/to/logs/wecom-mcp-server.out.log
environment=LANG="en_US.UTF-8",PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin",PYTHONUNBUFFERED="1",WECOM_API_TRUST_ENV="false",no_proxy="*",NO_PROXY="*",WECOM_MCP_HOST="127.0.0.1",WECOM_MCP_PORT="8101"Common commands:
supervisorctl status wecom-mcp-server # Check status
supervisorctl restart wecom-mcp-server # Restart (required after editing .env)
supervisorctl stop wecom-mcp-server # Stop
supervisorctl reread && supervisorctl update # Reload after config changesConfiguration notes
Item | Value | Notes |
Command |
| Launch wrapper that clears stale port holders before starting the server (see below) |
|
| Start automatically with the system |
Bind address |
| The MCP protocol has no authentication — binding |
| must include | On macOS |
Egress |
| Required, see below |
| need |
|
⚠️ Why a launch wrapper is needed
When supervisord is SIGKILLed (rather than shut down gracefully), the children it spawned
do not exit with it — they become orphans that keep holding the TCP port. The next
supervisord instance then fails to bind with address already in use and loops forever in
STARTING, never actually serving.
scripts/run_mcp_server.sh handles this in two steps:
Locate whatever holds the target port via
lsof, cross-check it against the PID file, and terminate only instances belonging to this project (matching working directory or PID file) — other services on the same port are never touched;Write
data/run/mcp_server.pidso the next start can identify leftovers quickly.
The script launches the real server via exec, so supervisord still tracks the server
process itself and stop/restart behave exactly as before.
⚠️ Why daemons must disable proxy inheritance
httpx honors HTTP_PROXY / HTTPS_PROXY by default. A daemon inherits the environment of
the session that launched it — if that proxy comes from a short-lived component (an IDE
sandbox, for example), it dies with the session, after which the service fails permanently
with All connection attempts failed, even though the very same command works fine from a
terminal.
The config therefore sets WECOM_API_TRUST_ENV=false and clears no_proxy, so the service
reaches the WeCom API directly and is never coupled to a transient session. If your production
environment genuinely requires a proxy, point environment at a stable proxy address instead.
Connecting an MCP client
streamable-http (default here):
{"type": "http", "url": "http://127.0.0.1:8101/mcp"}stdio: no supervisord needed — the MCP client spawns the process itself. Keep
WECOM_API_TRUST_ENV=true(or simply leave the proxy variables unset).
Single-instance constraint
The message and approval databases (data/*.db) are local SQLite files that only one
process may write. Do not run a supervisord instance alongside a manually started
process — concurrent writers can trigger database lock contention.
The launch wrapper automatically clears stale instances belonging to this project, but never touches processes you started yourself — if a manual instance is holding the port, the managed one logs a warning and fails to bind.
📁 Project Structure
wecom-mcp-server/
├── src/
│ └── wecom_mcp_server/
│ ├── __init__.py
│ ├── server.py # MCP Server entry point, tool registration
│ ├── config.py # Configuration management (pydantic-settings)
│ ├── aes.py # Pure-Python AES-256-CBC implementation
│ ├── crypto.py # WeCom message encryption/decryption logic
│ ├── wecom_client.py # WeCom API async client (incl. approval open APIs)
│ ├── webhook.py # Message reception webhook service
│ ├── storage.py # SQLite message persistence & file management
│ ├── approval.py # Approval domain layer: control parsing, flow parsing, collector
│ ├── approval_storage.py # Approval persistence & query (SQLite)
│ ├── approval_export.py # Approval export (CSV/XLSX/JSON) & statistics
│ └── xlsx_writer.py # Dependency-free XLSX writer
├── tests/
│ ├── fixtures/ # Sample approval payloads
│ ├── test_approval.py # Offline approval tests (no WeCom credentials needed)
│ ├── test_mcp_live.py # MCP protocol integration test (streamable-http)
│ └── test_mcp_stdio.py # MCP protocol integration test (stdio)
├── scripts/
│ └── run_mcp_server.sh # supervisord launch wrapper (clears stale port holders)
├── .env.example # Environment variable example
├── pyproject.toml # Project dependencies & build config
└── README.md # This document🔑 Configuration Notes
WeCom App Permissions
Basic Sending Permission: Create a self-built app in the WeCom admin backend and obtain
CorpID,Secret, andAgentId.Contact Reading Permission (optional): Enable
API Interface Syncin "Management Tools → Contact Sync" to getcontacts_secret. Without it, only members within the app's visible scope can be read.Message Reception (optional): Enable API reception in the app's "Receive Messages" settings to get
TokenandEncodingAESKey. For local development, use an intranet tunnel tool (e.g., ngrok, cloudflared) to expose the callback address.Approval Data Permission: In "App Management → Approval → API", add the self-built app to "Callable Apps" and grant the approval data permission — see Approval Feature.
Data Storage Structure
data/
├── wecom_messages.db # SQLite database (messages, yearly partitioned)
├── wecom_approvals.db # SQLite database (approvals + template metadata)
├── exports/ # Approval exports (csv / xlsx / json)
└── files/
├── 2026/
│ ├── 07/
│ │ ├── image_xxx.jpg
│ │ └── report.pdf
│ └── ...
└── ...Running Tests
uv run python tests/test_approval.py📜 License
Available Tools
8 toolscheck_configA
检查当前 WeCom 配置是否就绪 (不发起网络请求)。
Inputs: no named parameters. Returns: result
list_users_by_departmentA
列出部门成员 (仅返回姓名 + UserID)。
Args: department_id: 部门 ID,0=根部门 (全部成员) fetch_child: 是否递归子部门
Inputs: fetch_child, department_id. Returns: result
lookup_user_idA
通过成员姓名查找 UserID (用于发送消息时指定接收人)。
支持精确匹配;若无人匹配则模糊匹配。多人同名会全部返回。
Inputs: name (required). Returns: result
mass_send_messageA
群发消息给应用可见范围内的全部成员 (touser=@all)。
注意: @all 仅覆盖应用可见范围,不是企业全部成员。 如需发给全企业,请把应用可见范围设为根部门。
Inputs: content (required), agent_id, msg_type. Returns: result
send_file_messageA
发送文件给企业微信成员 (自动上传文件并发送)。
限制: 文件不超过 20MB。media_id 仅 3 天内有效。
Inputs: file_path (required), to_tag, to_user, agent_id, +1 more. Returns: result
send_markdown_messageA
发送 Markdown 消息 (仅企业微信客户端支持)。
content 示例: # 标题 / 加粗 / 链接 / > 引用
Inputs: to_tag, content, to_user, agent_id, +1 more. Returns: result
send_messageA
发送任意类型应用消息 (通用接口)。
Args: msg_type: text/markdown/textcard/news/image/mpnews/template_card 等 content_json: 对应 msg_type 的内容体 JSON。 text: {"content":"文本"} markdown: {"content":"# 标题"} textcard: {"title":"标题","description":"描述","url":"https://..."} agent_id: 应用 AgentId,留空使用默认配置
Inputs: to_tag, to_user, agent_id, msg_type, +2 more. Returns: result
send_text_messageB
向企业微信成员发送文本消息。
Args: to_user: 接收人 userid,多人用 "|" 分隔 to_party: 接收部门 id,多个用 "|" 分隔 to_tag: 接收标签 id,多个用 "|" 分隔 content: 文本内容 agent_id: 应用 AgentId,留空使用默认配置
Inputs: to_tag, content, to_user, agent_id, +1 more. Returns: result
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 8 tools
Most tools have clearly distinct purposes, but send_message can also send text/markdown, overlapping with send_text_message and send_markdown_message. The generic tool is described as a universal interface, but the overlap could still cause selection confusion.
All tools follow a consistent verb_noun snake_case pattern: send_text_message, send_markdown_message, lookup_user_id, list_users_by_department, check_config. The only slight deviation is send_message, but it is still a clear verb_noun form.
8 tools is well-scoped for a WeCom messaging integration. Each tool covers a distinct function: sending different message types, mass send, user lookup, department listing, and configuration check, without unnecessary bloat.
Core messaging workflows are covered: text, markdown, file, generic types, mass send, and user retrieval. Minor gaps include lack of a dedicated media upload tool for images/news and no message status query, but these are not critical for a simple messaging server.
Maintenance
Related MCP Connectors
Messaging tools for AI agents: send messages, manage chats, groups and channels.
Context and control for any AI. Agent identity, scoped access, and proof across tools.
WhatsApp CRM for AI agents: search contacts, read chats, manage the sales pipeline, send messages.
WhatsApp for Claude, ChatGPT or any AI agent: search, reply and send. Personal or Business API.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceEnables AI assistants to send WeChat messages through automation, supporting both immediate and scheduled message delivery to contacts and groups using the Model Context Protocol standard.37-
- AlicenseNot gradedqualityBmaintenanceEnables AI assistants to interact with Enterprise WeChat (WeCom) using natural language to manage meetings, book rooms, and search contacts. It supports multiple AI models and provides intelligent contact matching via Pinyin.1MIT
- AlicenseBqualityDmaintenanceProvides WeChat automation capabilities for AI development tools via the Model Context Protocol. It enables users to send messages, manage contacts, and handle file transfers through AI assistants like Claude and Cursor.2735MIT
- AlicenseNot gradedqualityDmaintenanceEnables AI coding agents like Cursor and Claude Code to interact with Feishu (Lark) via the Model Context Protocol. It supports managing documents, sending messages, and searching knowledge bases directly through natural language commands.327 npmMIT