douyin-devtools-mcp
v0.0.1
Published
MCP server for Douyin Mini Program automation via Chrome DevTools Protocol
Maintainers
Readme
douyin-devtools-mcp
通过 MCP 协议把抖音小程序的自动化能力暴露给 Claude / Cursor / 任何 LLM Agent。
架构:Agent → MCP Server → 抖音开发者工具的 Chrome DevTools Protocol → MiniApp Webview。
零 SDK 依赖:不需要 tt-automator(它不存在),直接连 IDE 内置的 CDP 端口。
当前状态(Phase 2 进行中)
| 能力 | 状态 | |------|------| | 自动发现 IDE CDP 端口 | ✅ | | 连接 MiniApp Webview / IDE Workbench(双 target) | ✅ | | 执行任意 JS(Runtime.evaluate) | ✅ | | 截图(Page.captureScreenshot) | ✅ | | 列出所有 target | ✅ | | 页面导航(走 IDE Compile Mode 工作流) | ✅ | | 启动参数热更新 | ✅ | | 元素点击(CSS selector + 视觉光标) | ✅ | | 元素输入(React 兼容 setter) | ✅ | | 等待元素出现/消失(轮询 + 超时) | ✅ | | 文本断言(equals / contains / regex) | ✅ | | 元素滑动 / 长按 | ⏳ Phase 2 续 | | 逻辑层 worker attach + Mock SDK | ⏳ Phase 3 |
12 个可用 Tool:
- 连接:
tt_auto_connect、tt_auto_disconnect、tt_list_targets - 观察:
tt_screenshot、tt_evaluate、tt_get_current_route - 导航:
tt_navigate_to、tt_update_launch_params - 操作:
tt_element_tap、tt_element_input - 等待 / 断言:
tt_wait_for_element、tt_assert_text
先决条件
- Node.js >= 18(需要原生
fetch和WebSocket,实测 v22 OK) - Windows / macOS / Linux(目前在 Windows 上完成 PoC,其他平台路径已写但未实测)
- 已安装并能正常运行的抖音开发者工具
- 一个已加载的小程序项目,模拟器可见
与开发流程的关系(职责边界)
MCP Server 只负责"连接已运行的 IDE + 操控小程序",不接管编译/构建。具体来说:
| 谁做 | 做什么 |
|------|--------|
| 你 | 启动小程序的编译产物(Taro / uni-app 的 watch、原生项目无需此步) |
| 你 | 启动抖音开发者工具,加载产物目录(如 dist/tt) |
| MCP Server | 通过 CDP 连接 IDE,执行 navigate / evaluate / screenshot 等操控 |
类比:Playwright 不负责启动你的 webpack,MCP 也不负责启动你的 Taro。
Node 多版本共存(Taro / uni-app 用户必看)
很多业务项目(尤其 Taro 3.x / uni-app)要用 Node 16 才能编译,但本 MCP Server 必须 Node ≥ 18。两套 runtime 完全可以并存,关键是别让它们抢同一个 PATH。
方案 A — 两个终端(开发时推荐,立刻能用)
终端 1(Node 16):
nvm use 16
cd D:\path\to\your-taro-project
npm run dev:tt # Taro watch,一直挂着
终端 2(Node 22):
nvm use 22
cd D:\myProject\douyinmcp
node tests/e2e-manual.mjs
抖音开发者工具:
自己开着,加载 dist/tt每个终端独立切版本,互不影响。
方案 B — Claude Desktop 配置里写死 Node 22 绝对路径(长期方案)
⚠️ 不要在 claude_desktop_config.json 里写 "command": "node" —— PATH 里第一个 node 是哪个版本不可控,nvm 切了一次就崩。直接给绝对路径:
{
"mcpServers": {
"douyin-devtools": {
"command": "D:\\software\\nvm\\v22.12.0\\node.exe",
"args": ["D:\\myProject\\douyinmcp\\dist\\index.js"]
}
}
}查具体路径:终端里 nvm use 22 && where node,把第一行复制进去(Windows 反斜杠记得双写)。
这样 Claude Desktop 永远用 Node 22 起 MCP,你在别的终端用 Node 16 跑 Taro,完全解耦。
安装与运行
git clone <repo>
cd douyin-devtools-mcp
npm install
npm run dev # tsx src/index.ts,stdio 模式
npm run typecheck # 类型检查
npm run build # 编译到 dist/在 Claude Desktop 里使用
编辑 %APPDATA%\Claude\claude_desktop_config.json(macOS:~/Library/Application Support/Claude/):
{
"mcpServers": {
"douyin-devtools": {
"command": "node",
"args": ["D:\\myProject\\douyinmcp\\dist\\index.js"]
}
}
}或者用 tsx 直接跑源码:
{
"mcpServers": {
"douyin-devtools": {
"command": "npx",
"args": ["tsx", "D:\\myProject\\douyinmcp\\src\\index.ts"]
}
}
}端到端冒烟测试
node tests/e2e-manual.mjs需要先手动打开抖音开发者工具并加载一个小程序项目。
Tool 速查
tt_auto_connect
连到正在运行的抖音开发者工具。端口自动发现(读 %APPDATA%\@byted\vela\DevToolsActivePort),通常不用传参数。
{ "host": "127.0.0.1", "port": 8803, "timeoutMs": 5000 }返回 targetId、targetTitle、webSocketDebuggerUrl、miniappOrigin。
tt_list_targets
诊断用 — 列出 CDP 上所有 target(包括 MiniApp Webview、IDE workbench、worker)。
tt_evaluate
在 MiniApp Webview 上下文执行任意 JS。
{
"expression": "document.title",
"awaitPromise": true,
"returnByValue": true,
"timeout": 10000
}⚠️ 当前连的是渲染层(WebView),tt.navigateTo 这类 SDK API 在逻辑层(Service Worker)。Phase 3 会做 worker attach;如果只是想换页,直接用 tt_navigate_to。
tt_screenshot
对当前 webview 截图。默认只写文件不返回 base64(避免撑爆 LLM 上下文)。
{
"path": "output/foo.png",
"format": "png", // 或 "jpeg"
"quality": 80, // jpeg 时
"includeBase64": false
}tt_navigate_to
切换小程序到指定页面。走的是 IDE 官方的 Compile Mode 工作流:在顶部"普通编译"下拉里新建一个编译模式 → 填模式名、启动页面、启动参数、进入场景 → 点确定 → IDE 自己重启小程序到目标页。
{
"url": "/pages/activity/sign-in/index?instanceId=abc",
"modeName": "mcp-nav-xxxxx", // 可选,不传则自动生成
"waitForRouteMs": 60000 // IDE 首次编译某页可能 30-60s
}⚠️ MiniApp Webview 里的 tt.navigateTo 是宿主 shim,不能真的换页;这是抖音 IDE 的设计,不是 bug。本 Tool 是唯一可靠的换页方式,稳定但慢(25-60s/次,取决于 IDE 编译时间)。
前置:目标页面必须已经在 dist/ 编译产物里(否则 IDE 启动页下拉里看不到这个 page)。
实现细节(踩坑沉淀):
- "普通编译"下拉用的 selector 必须过滤掉空文本的 select(IDE 工具栏里有好几个图标按钮也是 Tila Select),否则会点错(如"清除缓存")
- 启动页面是 search-on-type 下拉,输入 pagePath + Enter 选中,不要滚虚拟列表 — 后者对深层路径不可靠
- 选完启动页面要验证 selection-text == pagePath,否则可能搜索没匹配上、流程静默继续用默认值,直到点确定才暴露
tt_update_launch_params
编辑已有编译模式的启动参数,只改 query 不重建模式。适合"重新跑一遍但换个 id"的场景。
{
"modeName": "mcp-nav-xxxxx",
"newQuery": "instanceId=123&a=2", // 不带前导 ?, 空串表示清空
"waitForRelaunchMs": 60000
}实现是定位下拉里该模式行的铅笔图标 → 打开编辑弹窗 → 只改第二个 input(启动参数)→ 确定。
tt_get_current_route
读 IDE 底部状态栏的当前页面路径。用于验证导航是否生效。无参数。
tt_element_tap
在 MiniApp Webview 上点击 CSS selector 命中的元素。默认开启视觉光标:派发点击前先用 Overlay.highlightRect 画红色高亮框停留 500ms,再真实派发鼠标事件 — 肉眼能跟得上,debug 友好。
{
"selector": ".btn-submit",
"index": 0, // 命中多个时取第几个
"showCursor": true, // CI 场景可关掉以加速
"cursorDurationMs": 500,
"scrollIntoView": true // 不在视口内先滚动到可见
}selector 解析路径:先查顶层 document(IDE shell),没命中再穿透 iframe[id^="miniapp-frame-"] 的 contentDocument(小程序业务渲染在那里)。坐标自动加 iframe offset,直接派发鼠标事件可用。
tt_auto_disconnect
断开 CDP 连接(不会关闭 IDE)。
端口发现的优先级
- 调用方显式传入的
port参数 - 环境变量
TT_AUTO_PORT ${TT_USER_DATA_DIR}/DevToolsActivePort- 平台默认 user-data-dir 下的
DevToolsActivePort:- Windows:
%APPDATA%\@byted\vela\ - macOS:
~/Library/Application Support/@byted/vela/ - Linux:
~/.config/@byted/vela/
- Windows:
抖音开发者工具的 CDP 端口是动态的(每次启动可能不一样),不要硬编码。
关键已知坑(填好了)
- Node 必须 ≥ 18,需要原生 fetch/WebSocket。Node 16 会报
fetch is not defined。 - 必须用
127.0.0.1不能用localhost— Node 22 默认走 IPv6,IDE 只监听 IPv4。 - CDP 端口不固定,从
DevToolsActivePort读取,不要假设是 8220/9420 之类。 - MCP 默认 30s 超时,截图、
tt_navigate_to等慢操作要在客户端侧加timeout参数。 - MiniApp Webview 是渲染层,小程序逻辑层在 worker target,需要分别 attach 才能调用
tt.*API(Phase 3)。 - 小程序业务渲染在同源 iframe
#miniapp-frame-N(blob: URL),不暴露为独立 CDP target,要从顶层contentDocument穿透。tt_element_tap已内置这个 fallback,自己写tt_evaluate时要注意。 - Overlay domain 依赖 DOM domain — 用
Overlay.highlightRect前必须先DOM.enable,否则静默失败。 tt.navigateTo在 Webview 里是宿主 shim,不会真的换页;要换页必须走 IDE Compile Mode(tt_navigate_to已封装)。tt_navigate_to走的是 IDE Workbench target(不是 Webview)—getWorkbenchSession()按workbenchMode=workbench标题匹配,首次连接要等 workbench 加载完成。- IDE 工具栏的 Tila Select 有好几个,光定位
.tila-select-selection会撞上"清除缓存"等图标按钮。selector 必须过滤掉 selection-text 为空的元素 + 锚定[class*=compile-mode]祖先 + 文本启发式兜底(见navigation.ts的COMPILE_MODE_SELECT_EXPR)。 - Tila Select 是 search-on-type 下拉,选项用输入 + Enter 选中,不要滚虚拟列表 — 深层路径(
pages/activity/sign-in/index这种)在初始可视区外,querySelectorAll 拿不到。select 选中后必须读.tila-select-selection-text验证,搜索无匹配会静默用默认值。
目录结构
src/
├── index.ts # MCP server entry(stdio transport)
├── server.ts # McpServer 实例 + Tool 注册
├── core/
│ ├── cdp-connection.ts # CDP 客户端 + 端口自动发现 + 多 target session 管理
│ ├── errors.ts # TT_E001..TT_E011 错误码
│ └── session.ts # 进程内 connection singleton + view/workbench session helper
└── tools/
├── connection.ts # tt_auto_connect / disconnect / list_targets
├── evaluate.ts # tt_evaluate
├── screenshot.ts # tt_screenshot
├── navigation.ts # tt_navigate_to / tt_update_launch_params / tt_get_current_route
├── element.ts # tt_element_tap / tt_element_input(含 iframe 穿透 + Overlay 视觉光标)
├── wait.ts # tt_wait_for_element(轮询 + 超时,iframe 穿透)
└── assert.ts # tt_assert_text(equals / contains / not_contains / regex)
tests/
└── e2e-manual.mjs # 端到端冒烟测试
poc/ # Phase 0 探测脚本和样本项目(保留作历史)
├── screenshot-test.mjs # 49 行 PoC 验证
└── hello-world/ # 最小抖音小程序示例后续 Phase
见 抖音小程序AI测试Agent_PRD_v1.0 (1).md 的第 15 章(v1.2 重构版里程碑)。下一阶段:
- Phase 2(进行中):元素操作扩展 —
tt_element_input/tt_element_swipe/tt_element_longpress,以及tt_element_inspect(只读不点) - Phase 3:逻辑层 worker target attach +
tt_call_api(直接调tt.*SDK)、Mock 注入 - Phase 4:
tt_analyze_project+tt_suggest_test_cases(模板规则,非 LLM) - Phase 5:
tt_run_test_case/tt_generate_report - Phase 6:CI/CD + npm 发布
License
MIT
