douyin-dm-mcp
Enables reading and sending Douyin private messages through a persistent browser profile, with dry-run and authorization safeguards.
README
douyin-dm-mcp
A Model Context Protocol server and local HTTP API for Douyin web direct messages, built with Playwright. Both interfaces reuse the same persistent local browser profile, read currently rendered conversations and messages, and send individual messages only when explicitly enabled.
The project uses Douyin's current standalone chat page:
https://www.douyin.com/chat?isPopup=1
Login and account status checks still use the Douyin home page. /messages currently returns a 404 page and is not used for automation.
Safety boundaries
DOUYIN_ALLOW_SENDdefaults tofalse, so real sending is disabled by default.send_messagedefaults todryRun: true. Dry runs validate the current snapshot without opening a conversation or changing page state.- A real send requires both dry-run to be disabled and
DOUYIN_ALLOW_SEND=true. - Before reading or a real send, the server verifies that the nickname is unique, the conversation position and exact nickname still match, and the open chat title matches.
- Duplicate nicknames are marked
targetable: falseand are refused by both MCP tools and the nickname-based CLI. - If the result cannot be confirmed after clicking send, the server returns
SEND_STATUS_UNKNOWNand does not retry automatically. - Each browser profile has an exclusive filesystem lock to prevent concurrent Chromium instances from corrupting it. MCP, the HTTP API, and the operator CLI cannot run at the same time against the same
DOUYIN_PROFILE. - All page operations are serialized to prevent cross-conversation reads or sends.
- The project does not modify browser fingerprints, bypass verification challenges, or call Douyin's private WebSocket/Protobuf interfaces.
- Logs are written to stderr and redact message bodies, cookies, and password fields.
Current limitations
Douyin's rendered conversation DOM does not expose a supported stable conversation ID, user ID, sec_uid, or stable profile link. Therefore:
conversationKeyis opaque and valid only for the latestlist_conversationssnapshot.- Calling
list_conversationscreates new keys and immediately expires every key from the previous snapshot. - Every conversation returns
stableKey: false; duplicate nicknames additionally returntargetable: false. - Call
list_conversationsbefore callingread_messagesorsend_message, then use a key from that exact result. - The conversation list contains only items currently rendered by the browser;
completeis alwaysfalse. - Fuzzy nickname matching, bulk sending, stranger search, and search-to-send fallbacks are intentionally unsupported.
Detailed live-page evidence is recorded in RESEARCH.md.
Requirements
- Node.js 20 or newer
- npm
- A desktop environment capable of displaying Chromium for the initial QR-code login
Installation
npm install
npx playwright install chromium
npm run build
Configuration
| Environment variable | Default | Description |
|---|---|---|
DOUYIN_PROFILE |
default |
Profile name; letters, numbers, underscores, and hyphens only |
DOUYIN_HEADLESS |
false |
Run Chromium headlessly; keep this false for initial login |
DOUYIN_ALLOW_SEND |
false |
Allow real message sends |
DOUYIN_DEBUG |
false |
Enable debug logging |
DOUYIN_NAVIGATION_TIMEOUT_MS |
60000 |
Navigation timeout in milliseconds |
DOUYIN_ACTION_TIMEOUT_MS |
10000 |
Page action timeout in milliseconds |
DOUYIN_MIN_SEND_INTERVAL_MS |
3000 |
Minimum interval between send attempts |
DOUYIN_API_HOST |
127.0.0.1 |
HTTP API bind address |
DOUYIN_API_PORT |
3000 |
HTTP API port |
DOUYIN_API_KEY |
unset | Bearer key, minimum 16 characters; required for non-loopback binding |
These variables are read from the process environment. The project does not load .env. Use .env.example as a reference, then export the values in your shell or set them in the MCP client env block.
Browser data is stored in:
.data/profiles/<DOUYIN_PROFILE>
This directory contains authentication data. Do not commit or share it.
Login
For first use or an expired session, run:
npm run login
Scan the displayed QR code with Douyin. After login, the script prints structured status, closes Chromium safely, and keeps the authenticated session in the persistent profile.
Check the current session:
npm run status
Example successful result:
{
"ok": true,
"browserRunning": true,
"loggedIn": true,
"currentUrl": "https://www.douyin.com/jingxuan"
}
Starting the MCP server
The compiled entry point is:
node dist/index.js
Codex CLI example:
codex mcp add douyin-dm -- node /absolute/path/to/douyin-dm-mcp/dist/index.js
Generic MCP client configuration:
{
"mcpServers": {
"douyin-dm": {
"command": "node",
"args": ["/absolute/path/to/douyin-dm-mcp/dist/index.js"],
"env": {
"DOUYIN_PROFILE": "default",
"DOUYIN_ALLOW_SEND": "false"
}
}
}
}
For an authorized real send, set DOUYIN_ALLOW_SEND to true for that MCP process and restart it. Do not leave sending globally enabled.
Do not start this process while the HTTP API or CLI already holds the same profile lock.
Starting the HTTP API
Run from source:
npm run api
Or run the compiled entry point:
node dist/api.js
Do not start this process while MCP or the CLI already holds the same profile lock.
The default base URL is http://127.0.0.1:3000. The unauthenticated health check is:
curl http://127.0.0.1:3000/health
API routes:
| Method | Path | Input | Purpose |
|---|---|---|---|
GET |
/health |
None | Process liveness; no auth, no browser |
GET |
/api/v1/status |
None | Login / browser session |
GET |
/api/v1/conversations?limit=20 |
Query parameter limit, 1–100 |
Current rendered snapshot + new keys |
POST |
/api/v1/messages/read |
JSON { "conversationKey": "...", "limit": 20 } |
Visible messages for a snapshot key |
POST |
/api/v1/messages/send |
JSON { "conversationKey": "...", "text": "...", "dryRun": true } |
Dry-run by default; real send needs both gates |
POST requests require Content-Type: application/json. Sending remains a dry run by default. A real send still requires both "dryRun": false and DOUYIN_ALLOW_SEND=true.
Example:
curl "http://127.0.0.1:3000/api/v1/conversations?limit=20"
curl -X POST http://127.0.0.1:3000/api/v1/messages/read \
-H "Content-Type: application/json" \
-d '{"conversationKey":"fallback:...:0","limit":20}'
Loopback access does not require an API key. Binding to any other host is refused unless DOUYIN_API_KEY is set to at least 16 characters. When configured, send it on every /api/v1/* request:
curl http://127.0.0.1:3000/api/v1/status \
-H "Authorization: Bearer YOUR_API_KEY"
The API returns the same structured success and Douyin error objects as MCP. Request parsing errors use INVALID_REQUEST, INVALID_JSON, UNSUPPORTED_MEDIA_TYPE, or PAYLOAD_TOO_LARGE; authentication failures use UNAUTHORIZED.
MCP tools
browser_status
Checks whether the persistent Douyin browser profile is authenticated.
Input: none.
list_conversations
Opens the standalone chat page and returns currently rendered conversations with opaque conversationKey values for the new snapshot.
{
"limit": 20
}
Conversation fields:
conversationKeystableKey, currently alwaysfalsepositionnicknamepreviewtimestamptargetable,falsewhen duplicate nicknames make safe selection impossible
read_messages
Reads currently visible messages from a conversation returned by list_conversations.
{
"conversationKey": "fallback:550e8400-e29b-41d4-a716-446655440000:0",
"limit": 20
}
Message fields:
direction:incomingoroutgoing, from verified sender-side DOM evidencetype:text, orunsupportedfor unrecognized message typescontent: visible text, ornullwhen empty
Conversations with targetable: false are refused.
send_message
Sends one message to a verified conversation.
{
"conversationKey": "fallback:550e8400-e29b-41d4-a716-446655440000:0",
"text": "Test message",
"dryRun": true
}
A real send requires all of the following:
DOUYIN_ALLOW_SEND=true.dryRun=false.- The target nickname is unique in the current snapshot.
- The conversation position and exact nickname still match the snapshot.
- The open chat title exactly matches the target nickname.
- The message has no leading or trailing whitespace.
- The logical Slate editor text exactly matches the requested text.
After clicking send, the server waits for a new outgoing message with the exact canonical text. If confirmation fails, it returns SEND_STATUS_UNKNOWN; callers must inspect the conversation manually instead of retrying automatically. The minimum send interval is retained across conversation-list refreshes.
Operator CLI
List currently rendered conversations:
npm run chat -- list
Read messages by an exact, unique nickname:
npm run chat -- read "Exact nickname"
Real sends also require DOUYIN_ALLOW_SEND. PowerShell example:
$env:DOUYIN_ALLOW_SEND="true"
npm run chat -- send "Exact nickname" "Test message"
Remove-Item Env:DOUYIN_ALLOW_SEND
The CLI accepts exact nicknames only and refuses to continue when no match or multiple matches are found.
Do not run the CLI while MCP or the HTTP API already holds the same profile lock.
Development
npm run lint
npm test
npm run build
npm run smoke:mcp
Tests cover configuration parsing, structured errors, profile locking, page-operation serialization, snapshot expiry, duplicate refusal, target verification, message direction, dry-run isolation, composer rollback, successful send confirmation, unknown send status, persistent rate limiting, and package-safe defaults.
Project structure
src/
browser/ Browser lifecycle, profile locking, and operation serialization
douyin/ DouyinService, centralized selectors, and page objects
index.ts MCP stdio server
api.ts HTTP API process entry point
api/ Versioned HTTP routes, validation, and authentication
scripts/
login.ts QR-code login
status.ts Authentication status check
chat.ts Operator CLI
mcp-smoke.ts MCP transport smoke check
tests/unit/ Repeatable behavioral tests
RESEARCH.md Live-page evidence and engineering research
License
Licensed under the permissive MIT License.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。