Files
auto_control/mcp_server/direct_ops.py
T

107 lines
3.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""MCP 直连操作封装:adb/u2/OCR/剪贴板——轻量通道优先。
为什么直连而不是全部走平台 HTTP:
- adb monkey 打开 App / am force-stop:一条 adb 命令,不建 u2 连接(省时省 token)
- 输入文字:u2 EditText.set_text 直接设文本(比剪贴板+粘贴少依赖)
- OCR:平台 RapidOCR(本地模型,截图即识别)
平台 REST 无法表达的操作(无独立端点)在此封装;安全(白名单/写门控)
仍在 MCP 工具层统一把关。绝不 kill-server / 绝不 disconnect(项目红线)。
"""
import re
import subprocess
from config import ADB_PATH
_log = None # mcp 层 logging 由调用方配置
def _adb(serial, *args, timeout=25):
"""对指定设备执行 adb shell 命令,返回输出文本(超时返回空)。"""
try:
r = subprocess.run([ADB_PATH, "-s", serial, "shell", *args],
capture_output=True, timeout=timeout)
return (r.stdout or b"").decode("utf-8", errors="replace")
except subprocess.TimeoutExpired:
return ""
except Exception:
return ""
def open_app(serial, package):
"""adb monkey 打开 App(无需知道 activity,最轻量)。"""
out = _adb(serial, "monkey", "-p", package,
"-c", "android.intent.category.LAUNCHER", "1")
return out or ""
def stop_app(serial, package):
"""强制停止 App(am force-stop)。"""
return _adb(serial, "am", "force-stop", package)
def foreground_app(serial):
"""当前前台 App 包名(dumpsys window 解析;失败返回空)。"""
out = _adb(serial, "dumpsys", "window", timeout=15)
m = re.search(r"mCurrentFocus=.*?([\w.]+)/", out or "")
return m.group(1) if m else ""
def list_apps(serial, keyword=""):
"""第三方已装应用包名列表(pm list packages -3,可关键词过滤)。"""
out = _adb(serial, "pm", "list", "packages", "-3")
pkgs = []
for line in (out or "").splitlines():
p = line.replace("package:", "").strip()
if p and (not keyword or keyword.lower() in p.lower()):
pkgs.append(p)
return pkgs
def type_text(serial, text):
"""向当前界面输入框输入文字(u2 定位 EditText set_text,支持中文)。
返回 (ok, msg)。
"""
import uiautomator2 as u2
d = u2.connect(serial)
# 优先聚焦输入框;找不到则第一个 EditText(与任务 input_text 兜底一致)
try:
el = d(focused=True)
if el.exists:
el.set_text(text)
return True, "已输入到聚焦输入框"
except Exception:
pass
try:
edit = d(className="android.widget.EditText")
if edit.exists:
edit.set_text(text)
return True, "已输入到输入框"
except Exception as e:
return False, f"输入失败: {type(e).__name__}: {str(e)[:100]}"
return False, "未找到输入框(请先点击输入框或提供界面信息)"
def set_clipboard(serial, text):
"""剪贴板注入(ClipInject 通道,读回验证)。"""
from core.clipboard_helper import inject_clipboard
return inject_clipboard(serial, text)
def ocr(serial):
"""截屏 + RapidOCR 识别,返回 [{text, score, box}]。"""
import uiautomator2 as u2
from core.ocr import recognize
d = u2.connect(serial)
img = d.screenshot()
if img is None:
return []
return recognize(img)
def read_clipboard(serial):
"""读设备剪贴板(u2)。"""
import uiautomator2 as u2
d = u2.connect(serial)
return d.clipboard or ""