1. 鸿蒙 Flutter 调试为什么需要 marionette_mcp
如果你正在做 Flutter 鸿蒙(HarmonyOS / OpenHarmony)跨平台应用,大概率遇到过这种场景:应用在鸿蒙真机上跑起来了,UI 看起来没问题,但某个按钮点不动、某个列表滚动异常、某个弹窗层级错乱。传统做法是加日志、打断点、反复热重载,一轮下来十几分钟就没了。更麻烦的是,当你想让 AI 代理帮你分析"为什么这个按钮点不动"时,AI 只能看到你贴过去的截图和日志片段,它根本不知道运行中的 Widget 树长什么样。
marionette_mcp 解决的正是这个问题。它是一个跑在 Flutter 应用内部的 MCP(Model Context Protocol)服务端实现,通过 Dart VM Service 协议把运行中应用的 UI 树快照、Widget 语义属性、交互接口暴露出来。AI 客户端(比如 Claude、Antigravity 这类支持 MCP 的工具)连上之后,就能"看到"鸿蒙手机当前屏幕上有哪些 Widget、每个 Widget 的 Key 和 Text 是什么、布局约束有没有冲突,甚至可以直接下发点击指令。
适合谁用?三类人:一是做鸿蒙 Flutter 自动化测试的,想让 AI 自动跑冒烟流程;二是做远程调试的,需要在不接触真机的情况下透视 UI 结构;三是做 AI 辅助开发的,希望把"运行中应用"变成 AI 可操作的对象。这篇会从依赖配置、TaoToken 统一 Key 接入、config.toml 与 settings.json 骨架、到实际验证请求,一步步走完。
2. TaoToken 前置:统一 Key 与 API 通道
marionette_mcp 本身只负责"应用内暴露接口",它不负责 AI 模型的调用。你需要一个 MCP 客户端去连接它,而客户端背后要调用大模型。这里就涉及 Key 管理的问题:如果你同时用 Claude、GPT、Gemini 做不同任务,每个平台一套 Key、一套计费、一套限流,调试链路会变得很碎。
TaoToken 的作用是把这些模型的调用收敛到一个统一入口。你只需要在 TaoToken 控制台创建一个 API Key,然后在 MCP 客户端里把 base_url 指向https://taotoken.net/api,模型名按需切换即可。这样 marionette_mcp 暴露的 UI 树数据,无论最终送给哪个模型分析,走的都是同一条通道,Key 也只需要维护一份。
具体操作:先到控制台创建 Key(地址是 https://taotoken.net/api-keys ),拿到形如sk-xxxx的字符串后保存好。如果你主要做长期编码和 Agent 任务,可以看下 Coding Plan 的额度说明( https://taotoken.net/coding-plan );如果只是临时验证模型对 UI 树的理解能力,用模型对话页面( https://taotoken.net/model-chat )先试一轮也行。接入文档在 https://taotoken.net/doc ,里面有针对不同客户端的配置示例。
注意:TaoToken 在这里的角色是模型调用的统一通道,不是"中转"marionette_mcp 的调试流量。marionette_mcp 的 MCP 连接是本地或局域网直连的,不要混淆这两条链路。
3. 可复制配置:pubspec、config.toml 与 settings.json
3.1 鸿蒙项目依赖与权限
在 Flutter 鸿蒙项目的pubspec.yaml里加入 marionette_mcp:
dependencies: flutter: sdk: flutter marionette_mcp: ^0.3.0鸿蒙端需要在module.json5里确认网络权限,否则外部 MCP 客户端连不上调试端口:
{ "module": { "requestPermissions": [ { "name": "ohos.permission.INTERNET" } ] } }Debug 模式下 Dart VM Service 是默认开启的,Profile 模式部分能力受限,Release 模式因为去掉了反射和 VM Service,marionette_mcp 基本不可用。所以这套链路只在 Debug/Profile 下跑。
3.2 应用内启动 MCP 服务
在main.dart里挂载服务,注意要在runApp之前启动:
import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:marionette_mcp/marionette_mcp.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); if (kDebugMode) { await MarionetteMcp.startServer(port: 8080); debugPrint('marionette_mcp 已启动,监听 8080'); } runApp(const MyApp()); }3.3 MCP 客户端 config.toml 骨架
假设你用的客户端支持 TOML 配置(很多 MCP 宿主都支持),骨架如下。这里把 marionette_mcp 作为本地 MCP server,把 TaoToken 作为模型通道:
[model] provider = "openai-compatible" base_url = "https://taotoken.net/api" api_key = "sk-你的TaoTokenKey" model = "claude-sonnet-4-20250514" [mcp_servers.marionette] command = "npx" args = ["-y", "marionette-mcp-client", "--host", "127.0.0.1", "--port", "8080"] transport = "stdio" [mcp_servers.marionette.env] MARIONETTE_TIMEOUT = "15000"3.4 settings.json 骨架
如果你的客户端用 JSON 配置,等价写法:
{ "model": { "baseUrl": "https://taotoken.net/api", "apiKey": "sk-你的TaoTokenKey", "modelName": "claude-sonnet-4-20250514" }, "mcpServers": { "marionette": { "command": "npx", "args": ["-y", "marionette-mcp-client", "--host", "127.0.0.1", "--port", "8080"], "env": { "MARIONETTE_TIMEOUT": "15000" } } } }3.5 鸿蒙真机端口转发
鸿蒙真机通过 USB 连接时,VM Service 的端口是随机的,marionette_mcp 依赖正确的 WebSocket 地址。用 hdc 做端口转发:
hdc fwd tcp:8080 tcp:8080执行后宿主机访问127.0.0.1:8080就能打到鸿蒙应用内部的 MCP 服务。如果端口被占用,换一个宿主机端口即可,比如hdc fwd tcp:9090 tcp:8080,同时把 config.toml 里的 port 改成 9090。
4. 验证请求:AI 代理连接与操控运行中应用
配置完成后,先做一次最小验证。启动鸿蒙应用,确认日志里出现"marionette_mcp 已启动"。然后在 MCP 客户端里发起一次 inspect 请求,让 AI 读取当前 UI 树。
4.1 验证 UI 树导出
在客户端里输入类似指令:
请调用 marionette 的 inspect_ui 工具,导出当前鸿蒙应用的 UI 树,并告诉我根节点下有几个 Scaffold。如果链路通了,你会看到返回的 JSON 结构里包含 Widget 类型、Key、Text、约束信息。这一步成功说明 marionette_mcp 服务端和 MCP 客户端之间的 stdio 通道是通的。
4.2 验证点击操控
给按钮加一个显式 Key,这是提升 AI 操控准确率的关键:
ElevatedButton( key: const Key('ohos_mcp_btn'), onPressed: () => debugPrint('按钮被 AI 点击'), child: const Text('AI 可感知按钮'), )然后让 AI 下发点击:
请调用 tap_widget,点击 Key 为 ohos_mcp_btn 的按钮,并确认日志里是否出现"按钮被 AI 点击"。4.3 验证模型通道
为了确认 TaoToken 这条通道也在工作,可以让 AI 在拿到 UI 树后做一次语义分析:
基于刚才导出的 UI 树,判断当前页面是否存在布局约束冲突,并给出可能的原因。如果模型能基于 UI 树数据给出合理分析,说明 marionette_mcp 的数据流和 TaoToken 的模型调用流都打通了。这一步的返回结果里,模型名、token 消耗都可以在 TaoToken 控制台的用量页面核对。
5. 本篇常见错排查
5.1 连接被拒绝或超时
最常见的原因是 hdc 端口转发没做,或者转发的端口和 config.toml 里写的不一致。先确认hdc fwd --list能看到转发规则,再确认应用日志里 marionette_mcp 监听的端口号。鸿蒙真机每次重连 USB 后端口可能变化,需要重新执行转发。
5.2 inspect_ui 返回空树
如果返回的 UI 树是空的,检查两点:一是WidgetsFlutterBinding.ensureInitialized()是否在startServer之前调用;二是应用是否已经完成首帧渲染。在runApp之后立刻调用 inspect 可能拿到空树,等页面稳定后再请求。
5.3 tap_widget 点击偏移
鸿蒙系统支持高刷新率,页面在做过渡动画时点击坐标可能偏移。建议在 tap 指令前加一个等待动画静止的检测,或者在 marionette_mcp 的调用参数里加大 timeout。实测下来,把MARIONETTE_TIMEOUT设到 15000ms 以上能明显减少误点。
5.4 模型返回 401 或 403
这是 TaoToken Key 的问题,不是 marionette_mcp 的问题。检查 config.toml 里的api_key是否完整、有没有多余空格,base_url 是否是https://taotoken.net/api(注意不要带 UTM 参数)。如果 Key 刚创建,等几秒再试。
5.5 Release 模式下服务起不来
这是预期行为。marionette_mcp 依赖 Dart VM Service,Release 模式去掉了这部分能力。确认你的构建命令是flutter build hap --debug或 profile 模式。
6. 继续深入:把调试链路固化下来
走到这里,你已经有了一个可复现的鸿蒙 Flutter AI 调试环境:应用内 marionette_mcp 暴露 UI 树和交互接口,MCP 客户端通过 hdc 转发连接,模型调用统一走 TaoToken。接下来可以做的几件事:把常用的 inspect 和 tap 指令封装成客户端里的快捷命令;给关键页面都加上显式 Key,让 AI 识别更稳;把 config.toml 和 settings.json 纳入版本管理,团队里其他人 clone 下来改个 Key 就能用。
如果你还没创建 TaoToken Key,可以从控制台入口进:https://taotoken.net/api-keys 。接入细节和不同客户端的配置差异,文档里写得更全:https://taotoken.net/doc 。长期跑编码和 Agent 任务的话,Coding Plan 的额度模型值得先看一眼:https://taotoken.net/coding-plan 。想先验证模型对 UI 树的理解能力,直接用模型对话页面贴一段 UI 树 JSON 进去问就行:https://taotoken.net/model-chat 。