From 02776b12b81f7a168103f12081077ad8c6a59399 Mon Sep 17 00:00:00 2001 From: butubb <1422726308@qq.com> Date: Fri, 4 Sep 2026 12:52:55 +0800 Subject: [PATCH] =?UTF-8?q?feat(M2):=20=E8=AF=AD=E4=B9=89=E5=B1=82?= =?UTF-8?q?=E5=B7=A5=E5=85=B7=20de=5Fui=5Ftree/de=5Ftap=5Felement=E2=80=94?= =?UTF-8?q?=E2=80=94UI=20=E6=A0=91=E5=85=83=E7=B4=A0=E5=AE=9A=E4=BD=8D?= =?UTF-8?q?=E7=82=B9=E5=87=BB=EF=BC=88text/id/desc=EF=BC=8C=E5=A4=9A?= =?UTF-8?q?=E5=91=BD=E4=B8=AD=20index=EF=BC=89=EF=BC=8C=E6=97=A0=E9=9C=80?= =?UTF-8?q?=E5=9D=90=E6=A0=87=E6=9B=B4=E5=8F=AF=E9=9D=A0=EF=BC=9B=E5=9D=90?= =?UTF-8?q?=E6=A0=87=20tap=20=E4=BF=9D=E7=95=99=E5=85=9C=E5=BA=95=EF=BC=88?= =?UTF-8?q?WebView/=E7=94=BB=E5=B8=83=E5=85=83=E7=B4=A0=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mcp_server/mcp_server.py | 61 +++++++++++++++++++++++++++++++++++ mcp_server/platform_client.py | 13 ++++++++ 2 files changed, 74 insertions(+) diff --git a/mcp_server/mcp_server.py b/mcp_server/mcp_server.py index a783e59..630fd24 100644 --- a/mcp_server/mcp_server.py +++ b/mcp_server/mcp_server.py @@ -167,6 +167,67 @@ _KEYS = ("back", "home", "recent", "menu", "power", "volume_up", "volume_down", "enter", "delete", "search", "camera") +@mcp.tool() +def de_ui_tree(serial: str) -> dict: + """获取当前界面元素树(文本 JSON):每元素含 text/resource_id/description/class/bounds。 + + 优先用它定位元素(元素驱动操作),比纯坐标点击更可靠。 + """ + try: + serial = _check_serial(serial) + els = platform().ui_elements(serial) + except PlatformError as e: + return _err(e) + # 精简输出:去掉 suggested/深度噪音,保留可定位属性 + slim = [] + for e in els: + slim.append({ + "text": e.get("text", "")[:50], + "id": e.get("resource_id", "")[:80], + "desc": e.get("description", "")[:50], + "class": e.get("class", "").split(".")[-1], + "bounds": e.get("bounds", ""), + }) + audit.audit("de_ui_tree", serial, "", f"{len(slim)} 元素") + return _ok({"count": len(slim), "elements": slim[:300]}) + + +@mcp.tool() +def de_tap_element(serial: str, by: str, value: str, index: int = 1) -> dict: + """按元素点击(不需要坐标):by=text|id|desc,value 为匹配文本/资源 id/描述。 + + 元素驱动操作比坐标可靠(界面变化自适应);元素不存在时返回错误, + 可改用 de_ui_tree 查元素或 de_tap 坐标兜底。index 用于多命中取第几个(默认 1)。 + """ + try: + _check_write() + serial = _check_serial(serial) + if by not in ("text", "id", "desc"): + raise PlatformError("invalid_param", "by 可选 text/id/desc") + if not value or index < 1: + raise PlatformError("invalid_param", "value 不能为空且 index>=1") + import uiautomator2 as u2 + d = u2.connect(serial) + kw = {"text": value} if by == "text" else ( + {"resourceId": value} if by == "id" else {"description": value}) + if index > 1: + kw["instance"] = index - 1 + el = d(**kw) + if not el.exists: + raise PlatformError("device_offline", + f"未找到元素({by}={value},index={index})——" + f"建议 de_ui_tree 查看实际元素或 de_tap 用坐标") + el.click() + except PlatformError as e: + return _err(e) + except Exception as e: + return _err(PlatformError("platform_unavailable", + f"元素点击失败: {type(e).__name__}: {str(e)[:100]}")) + audit.audit("de_tap_element", serial, f"{by}={value}#{index}", "ok") + return _ok({"action": "tap_element", "serial": serial, + "by": by, "value": value, "index": index}) + + @mcp.tool() def de_wake(serial: str) -> dict: """点亮设备屏幕并解锁(熄屏时先调用它再截图)。""" diff --git a/mcp_server/platform_client.py b/mcp_server/platform_client.py index cb8cadc..761b457 100644 --- a/mcp_server/platform_client.py +++ b/mcp_server/platform_client.py @@ -107,6 +107,19 @@ class PlatformClient: "duration": float(duration)}) return self._check_op(r, "swipe") + def ui_elements(self, serial): + """UI 元素树(GET /api/uiauto/elements,uiautodev 服务)。""" + r = self._get("/api/uiauto/elements", serial=serial) + if r.status_code == 503: + raise PlatformError("device_offline", r.text[:120]) + if r.status_code != 200: + raise PlatformError("platform_unavailable", + f"元素树 HTTP {r.status_code}") + j = r.json() or {} + if not j.get("ok"): + raise PlatformError("device_offline", str(j.get("error", "取元素失败"))[:120]) + return j.get("elements", []) + def wake(self, serial): """亮屏并解锁(POST /api/device/screen_all mode=on)。""" r = self._post("/api/device/screen_all",