mobile-mcp-ai 2.5.3__tar.gz → 2.5.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mobile_mcp_ai-2.5.3/mobile_mcp_ai.egg-info → mobile_mcp_ai-2.5.5}/PKG-INFO +14 -1
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/README.md +13 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/__init__.py +1 -1
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/core/basic_tools_lite.py +42 -7
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/mcp_tools/mcp_server.py +83 -9
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5/mobile_mcp_ai.egg-info}/PKG-INFO +14 -1
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/mobile_mcp_ai.egg-info/SOURCES.txt +0 -10
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/setup.py +1 -1
- mobile_mcp_ai-2.5.3/tests/test_mind_cloud_my_space.py +0 -80
- mobile_mcp_ai-2.5.3/tests/test_mind_correct.py +0 -73
- mobile_mcp_ai-2.5.3/tests/test_mind_improved.py +0 -83
- mobile_mcp_ai-2.5.3/tests/test_mind_optimized.py +0 -77
- mobile_mcp_ai-2.5.3/tests/test_open_mind.py +0 -37
- mobile_mcp_ai-2.5.3/tests/test_priority_demo.py +0 -81
- mobile_mcp_ai-2.5.3/tests/test_simple.py +0 -76
- mobile_mcp_ai-2.5.3/tests/test_/344/270/276/346/212/245.py +0 -136
- mobile_mcp_ai-2.5.3/tests/test_/345/210/207/346/215/242/350/257/255/350/250/200/345/210/260English.py +0 -158
- mobile_mcp_ai-2.5.3/tests/test_/346/265/213/350/257/225.py +0 -114
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/LICENSE +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/MANIFEST.in +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/config.py +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/core/__init__.py +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/core/device_manager.py +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/core/dynamic_config.py +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/core/ios_client_wda.py +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/core/ios_device_manager_wda.py +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/core/mobile_client.py +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/core/template_matcher.py +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/core/templates/close_buttons/auto_x_0112_151217.png +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/core/templates/close_buttons/auto_x_0112_152037.png +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/core/templates/close_buttons/auto_x_0112_152840.png +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/core/templates/close_buttons/auto_x_0112_153256.png +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/core/templates/close_buttons/auto_x_0112_154847.png +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/core/templates/close_buttons/gray_x_stock_ad.png +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/core/utils/__init__.py +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/core/utils/logger.py +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/core/utils/operation_history_manager.py +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/core/utils/smart_wait.py +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/docs/iOS_SETUP_GUIDE.md +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/mcp_tools/__init__.py +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/mobile_mcp_ai.egg-info/dependency_links.txt +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/mobile_mcp_ai.egg-info/entry_points.txt +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/mobile_mcp_ai.egg-info/not-zip-safe +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/mobile_mcp_ai.egg-info/requires.txt +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/mobile_mcp_ai.egg-info/top_level.txt +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/requirements.txt +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/setup.cfg +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/templates/close_buttons/auto_x_0112_151217.png +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/templates/close_buttons/auto_x_0112_152037.png +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/templates/close_buttons/auto_x_0112_152840.png +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/templates/close_buttons/auto_x_0112_153256.png +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/templates/close_buttons/auto_x_0112_154847.png +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/templates/close_buttons/gray_x_stock_ad.png +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/utils/__init__.py +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/utils/logger.py +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/utils/xml_formatter.py +0 -0
- {mobile_mcp_ai-2.5.3 → mobile_mcp_ai-2.5.5}/utils/xml_parser.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mobile-mcp-ai
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.5
|
|
4
4
|
Summary: 移动端自动化 MCP Server - 支持 Android/iOS,AI 功能可选(基础工具不需要 AI)
|
|
5
5
|
Home-page: https://github.com/test111ddff-hash/mobile-mcp-ai
|
|
6
6
|
Author: douzi
|
|
@@ -408,10 +408,18 @@ tidevice list
|
|
|
408
408
|
|:---:|------|------|
|
|
409
409
|
| 📋 | `mobile_list_elements` | 列出页面元素 |
|
|
410
410
|
| 📸 | `mobile_take_screenshot` | 截图 |
|
|
411
|
+
| 📸 | `mobile_screenshot_with_som` | Set-of-Mark 截图(智能标注) |
|
|
412
|
+
| 📸 | `mobile_screenshot_with_grid` | 带网格坐标的截图 |
|
|
411
413
|
| 📐 | `mobile_get_screen_size` | 屏幕尺寸 |
|
|
412
414
|
| 👆 | `mobile_click_by_text` | 文本点击 |
|
|
413
415
|
| 👆 | `mobile_click_by_id` | ID 点击 |
|
|
414
416
|
| 👆 | `mobile_click_at_coords` | 坐标点击 |
|
|
417
|
+
| 👆 | `mobile_click_by_percent` | 百分比点击 |
|
|
418
|
+
| 👆 | `mobile_click_by_som` | SoM 编号点击 |
|
|
419
|
+
| 👆 | `mobile_long_press_by_id` | ID 长按 |
|
|
420
|
+
| 👆 | `mobile_long_press_by_text` | 文本长按 |
|
|
421
|
+
| 👆 | `mobile_long_press_by_percent` | 百分比长按 |
|
|
422
|
+
| 👆 | `mobile_long_press_at_coords` | 坐标长按 |
|
|
415
423
|
| ⌨️ | `mobile_input_text_by_id` | ID 输入 |
|
|
416
424
|
| ⌨️ | `mobile_input_at_coords` | 坐标输入 |
|
|
417
425
|
| 👆 | `mobile_swipe` | 滑动 |
|
|
@@ -422,6 +430,11 @@ tidevice list
|
|
|
422
430
|
| 📦 | `mobile_list_apps` | 列出应用 |
|
|
423
431
|
| 📱 | `mobile_list_devices` | 列出设备 |
|
|
424
432
|
| 🔌 | `mobile_check_connection` | 检查连接 |
|
|
433
|
+
| 🔍 | `mobile_find_close_button` | 查找关闭按钮 |
|
|
434
|
+
| 🚫 | `mobile_close_popup` | 关闭弹窗 |
|
|
435
|
+
| 🚫 | `mobile_close_ad` | 智能关闭广告弹窗 |
|
|
436
|
+
| 🎯 | `mobile_template_close` | 模板匹配关闭弹窗 |
|
|
437
|
+
| ➕ | `mobile_template_add` | 添加 X 号模板 |
|
|
425
438
|
| ✅ | `mobile_assert_text` | 断言文本 |
|
|
426
439
|
| 📜 | `mobile_get_operation_history` | 操作历史 |
|
|
427
440
|
| 🗑️ | `mobile_clear_operation_history` | 清空历史 |
|
|
@@ -337,10 +337,18 @@ tidevice list
|
|
|
337
337
|
|:---:|------|------|
|
|
338
338
|
| 📋 | `mobile_list_elements` | 列出页面元素 |
|
|
339
339
|
| 📸 | `mobile_take_screenshot` | 截图 |
|
|
340
|
+
| 📸 | `mobile_screenshot_with_som` | Set-of-Mark 截图(智能标注) |
|
|
341
|
+
| 📸 | `mobile_screenshot_with_grid` | 带网格坐标的截图 |
|
|
340
342
|
| 📐 | `mobile_get_screen_size` | 屏幕尺寸 |
|
|
341
343
|
| 👆 | `mobile_click_by_text` | 文本点击 |
|
|
342
344
|
| 👆 | `mobile_click_by_id` | ID 点击 |
|
|
343
345
|
| 👆 | `mobile_click_at_coords` | 坐标点击 |
|
|
346
|
+
| 👆 | `mobile_click_by_percent` | 百分比点击 |
|
|
347
|
+
| 👆 | `mobile_click_by_som` | SoM 编号点击 |
|
|
348
|
+
| 👆 | `mobile_long_press_by_id` | ID 长按 |
|
|
349
|
+
| 👆 | `mobile_long_press_by_text` | 文本长按 |
|
|
350
|
+
| 👆 | `mobile_long_press_by_percent` | 百分比长按 |
|
|
351
|
+
| 👆 | `mobile_long_press_at_coords` | 坐标长按 |
|
|
344
352
|
| ⌨️ | `mobile_input_text_by_id` | ID 输入 |
|
|
345
353
|
| ⌨️ | `mobile_input_at_coords` | 坐标输入 |
|
|
346
354
|
| 👆 | `mobile_swipe` | 滑动 |
|
|
@@ -351,6 +359,11 @@ tidevice list
|
|
|
351
359
|
| 📦 | `mobile_list_apps` | 列出应用 |
|
|
352
360
|
| 📱 | `mobile_list_devices` | 列出设备 |
|
|
353
361
|
| 🔌 | `mobile_check_connection` | 检查连接 |
|
|
362
|
+
| 🔍 | `mobile_find_close_button` | 查找关闭按钮 |
|
|
363
|
+
| 🚫 | `mobile_close_popup` | 关闭弹窗 |
|
|
364
|
+
| 🚫 | `mobile_close_ad` | 智能关闭广告弹窗 |
|
|
365
|
+
| 🎯 | `mobile_template_close` | 模板匹配关闭弹窗 |
|
|
366
|
+
| ➕ | `mobile_template_add` | 添加 X 号模板 |
|
|
354
367
|
| ✅ | `mobile_assert_text` | 断言文本 |
|
|
355
368
|
| 📜 | `mobile_get_operation_history` | 操作历史 |
|
|
356
369
|
| 🗑️ | `mobile_clear_operation_history` | 清空历史 |
|
|
@@ -1587,8 +1587,14 @@ class BasicMobileToolsLite:
|
|
|
1587
1587
|
|
|
1588
1588
|
# ==================== 导航操作 ====================
|
|
1589
1589
|
|
|
1590
|
-
async def swipe(self, direction: str) -> Dict:
|
|
1591
|
-
"""滑动屏幕
|
|
1590
|
+
async def swipe(self, direction: str, y: Optional[int] = None, y_percent: Optional[float] = None) -> Dict:
|
|
1591
|
+
"""滑动屏幕
|
|
1592
|
+
|
|
1593
|
+
Args:
|
|
1594
|
+
direction: 滑动方向 (up/down/left/right)
|
|
1595
|
+
y: 左右滑动时指定的高度坐标(像素)
|
|
1596
|
+
y_percent: 左右滑动时指定的高度百分比 (0-100)
|
|
1597
|
+
"""
|
|
1592
1598
|
try:
|
|
1593
1599
|
if self._is_ios():
|
|
1594
1600
|
ios_client = self._get_ios_client()
|
|
@@ -1602,11 +1608,26 @@ class BasicMobileToolsLite:
|
|
|
1602
1608
|
|
|
1603
1609
|
center_x, center_y = width // 2, height // 2
|
|
1604
1610
|
|
|
1611
|
+
# 对于左右滑动,如果指定了 y 或 y_percent,使用指定的高度
|
|
1612
|
+
if direction in ['left', 'right']:
|
|
1613
|
+
if y_percent is not None:
|
|
1614
|
+
if not (0 <= y_percent <= 100):
|
|
1615
|
+
return {"success": False, "message": f"❌ y_percent 必须在 0-100 之间: {y_percent}"}
|
|
1616
|
+
swipe_y = int(height * y_percent / 100)
|
|
1617
|
+
elif y is not None:
|
|
1618
|
+
if not (0 <= y <= height):
|
|
1619
|
+
return {"success": False, "message": f"❌ y 坐标超出屏幕范围 (0-{height}): {y}"}
|
|
1620
|
+
swipe_y = y
|
|
1621
|
+
else:
|
|
1622
|
+
swipe_y = center_y
|
|
1623
|
+
else:
|
|
1624
|
+
swipe_y = center_y
|
|
1625
|
+
|
|
1605
1626
|
swipe_map = {
|
|
1606
1627
|
'up': (center_x, int(height * 0.8), center_x, int(height * 0.2)),
|
|
1607
1628
|
'down': (center_x, int(height * 0.2), center_x, int(height * 0.8)),
|
|
1608
|
-
'left': (int(width * 0.8),
|
|
1609
|
-
'right': (int(width * 0.2),
|
|
1629
|
+
'left': (int(width * 0.8), swipe_y, int(width * 0.2), swipe_y),
|
|
1630
|
+
'right': (int(width * 0.2), swipe_y, int(width * 0.8), swipe_y),
|
|
1610
1631
|
}
|
|
1611
1632
|
|
|
1612
1633
|
if direction not in swipe_map:
|
|
@@ -1619,9 +1640,23 @@ class BasicMobileToolsLite:
|
|
|
1619
1640
|
else:
|
|
1620
1641
|
self.client.u2.swipe(x1, y1, x2, y2, duration=0.5)
|
|
1621
1642
|
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1643
|
+
# 记录操作信息
|
|
1644
|
+
record_info = {'direction': direction}
|
|
1645
|
+
if y is not None:
|
|
1646
|
+
record_info['y'] = y
|
|
1647
|
+
if y_percent is not None:
|
|
1648
|
+
record_info['y_percent'] = y_percent
|
|
1649
|
+
self._record_operation('swipe', **record_info)
|
|
1650
|
+
|
|
1651
|
+
# 构建返回消息
|
|
1652
|
+
msg = f"✅ 滑动成功: {direction}"
|
|
1653
|
+
if direction in ['left', 'right']:
|
|
1654
|
+
if y_percent is not None:
|
|
1655
|
+
msg += f" (高度: {y_percent}% = {swipe_y}px)"
|
|
1656
|
+
elif y is not None:
|
|
1657
|
+
msg += f" (高度: {y}px)"
|
|
1658
|
+
|
|
1659
|
+
return {"success": True, "message": msg}
|
|
1625
1660
|
except Exception as e:
|
|
1626
1661
|
return {"success": False, "message": f"❌ 滑动失败: {e}"}
|
|
1627
1662
|
|
|
@@ -34,9 +34,21 @@ from pathlib import Path
|
|
|
34
34
|
from typing import Optional
|
|
35
35
|
|
|
36
36
|
# 添加项目根目录到 Python 路径
|
|
37
|
-
#
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
# 支持两种运行方式:
|
|
38
|
+
# 1. 从源码运行:__file__ 在 mcp_tools/ 目录下,往上两级到项目根目录
|
|
39
|
+
# 2. 从已安装包运行:包已安装时,mobile_mcp 应该可以直接导入
|
|
40
|
+
# 先尝试从已安装的包导入,如果失败则从源码路径导入
|
|
41
|
+
try:
|
|
42
|
+
# 尝试导入已安装的包
|
|
43
|
+
import mobile_mcp.core.mobile_client
|
|
44
|
+
import mobile_mcp.core.basic_tools_lite
|
|
45
|
+
# 如果成功,说明包已安装,不需要添加路径
|
|
46
|
+
except ImportError:
|
|
47
|
+
# 包未安装或导入失败,从源码运行
|
|
48
|
+
# __file__ 在 mcp_tools/ 目录下,往上两级到项目根目录
|
|
49
|
+
project_root = Path(__file__).parent.parent
|
|
50
|
+
if str(project_root) not in sys.path:
|
|
51
|
+
sys.path.insert(0, str(project_root))
|
|
40
52
|
|
|
41
53
|
# 尝试导入 MCP,处理可能的路径冲突
|
|
42
54
|
try:
|
|
@@ -100,15 +112,34 @@ class MobileMCPServer:
|
|
|
100
112
|
|
|
101
113
|
async def initialize(self):
|
|
102
114
|
"""延迟初始化设备连接"""
|
|
103
|
-
#
|
|
115
|
+
# 如果已成功初始化,检查连接是否仍然有效
|
|
104
116
|
if self._initialized and self.tools is not None:
|
|
105
|
-
|
|
117
|
+
# 验证设备连接是否仍然有效
|
|
118
|
+
if self._is_connection_valid():
|
|
119
|
+
return
|
|
120
|
+
else:
|
|
121
|
+
# 连接已失效,重置状态
|
|
122
|
+
print("⚠️ 检测到设备连接已断开,正在重新连接...", file=sys.stderr)
|
|
123
|
+
self._initialized = False
|
|
124
|
+
self.client = None
|
|
125
|
+
self.tools = None
|
|
106
126
|
|
|
107
127
|
platform = self._detect_platform()
|
|
108
128
|
|
|
109
129
|
try:
|
|
110
|
-
|
|
111
|
-
|
|
130
|
+
# 尝试导入,如果失败会抛出 ImportError
|
|
131
|
+
try:
|
|
132
|
+
from mobile_mcp.core.mobile_client import MobileClient
|
|
133
|
+
from mobile_mcp.core.basic_tools_lite import BasicMobileToolsLite
|
|
134
|
+
except ImportError as import_err:
|
|
135
|
+
# 如果导入失败,尝试从源码路径导入
|
|
136
|
+
# 这通常发生在开发模式下,包未安装时
|
|
137
|
+
project_root = Path(__file__).parent.parent
|
|
138
|
+
if str(project_root) not in sys.path:
|
|
139
|
+
sys.path.insert(0, str(project_root))
|
|
140
|
+
# 再次尝试导入
|
|
141
|
+
from mobile_mcp.core.mobile_client import MobileClient
|
|
142
|
+
from mobile_mcp.core.basic_tools_lite import BasicMobileToolsLite
|
|
112
143
|
|
|
113
144
|
self.client = MobileClient(platform=platform)
|
|
114
145
|
self.tools = BasicMobileToolsLite(self.client)
|
|
@@ -122,6 +153,33 @@ class MobileMCPServer:
|
|
|
122
153
|
self._last_error = error_msg # 保存错误信息
|
|
123
154
|
# 不设置 _initialized = True,下次调用会重试
|
|
124
155
|
|
|
156
|
+
def _is_connection_valid(self) -> bool:
|
|
157
|
+
"""检查设备连接是否仍然有效"""
|
|
158
|
+
try:
|
|
159
|
+
if self.client is None:
|
|
160
|
+
return False
|
|
161
|
+
|
|
162
|
+
# Android: 检查 u2 连接
|
|
163
|
+
if hasattr(self.client, 'u2') and self.client.u2:
|
|
164
|
+
# 尝试获取设备信息,如果失败说明连接断开
|
|
165
|
+
self.client.u2.info
|
|
166
|
+
return True
|
|
167
|
+
|
|
168
|
+
# iOS: 检查 wda 连接
|
|
169
|
+
if hasattr(self.client, 'wda') and self.client.wda:
|
|
170
|
+
self.client.wda.status()
|
|
171
|
+
return True
|
|
172
|
+
|
|
173
|
+
# iOS (通过 _ios_client)
|
|
174
|
+
if hasattr(self.client, '_ios_client') and self.client._ios_client:
|
|
175
|
+
if hasattr(self.client._ios_client, 'wda') and self.client._ios_client.wda:
|
|
176
|
+
self.client._ios_client.wda.status()
|
|
177
|
+
return True
|
|
178
|
+
|
|
179
|
+
return False
|
|
180
|
+
except Exception:
|
|
181
|
+
return False
|
|
182
|
+
|
|
125
183
|
def _detect_platform(self) -> str:
|
|
126
184
|
"""自动检测设备平台"""
|
|
127
185
|
platform = os.getenv("MOBILE_PLATFORM", "").lower()
|
|
@@ -445,7 +503,11 @@ class MobileMCPServer:
|
|
|
445
503
|
# ==================== 导航操作 ====================
|
|
446
504
|
tools.append(Tool(
|
|
447
505
|
name="mobile_swipe",
|
|
448
|
-
description="👆 滑动屏幕。方向:up/down/left/right"
|
|
506
|
+
description="👆 滑动屏幕。方向:up/down/left/right\n\n"
|
|
507
|
+
"💡 左右滑动时,可指定高度坐标或百分比:\n"
|
|
508
|
+
"- y: 指定高度坐标(像素)\n"
|
|
509
|
+
"- y_percent: 指定高度百分比 (0-100)\n"
|
|
510
|
+
"- 两者都未指定时,使用屏幕中心高度",
|
|
449
511
|
inputSchema={
|
|
450
512
|
"type": "object",
|
|
451
513
|
"properties": {
|
|
@@ -453,6 +515,14 @@ class MobileMCPServer:
|
|
|
453
515
|
"type": "string",
|
|
454
516
|
"enum": ["up", "down", "left", "right"],
|
|
455
517
|
"description": "滑动方向"
|
|
518
|
+
},
|
|
519
|
+
"y": {
|
|
520
|
+
"type": "integer",
|
|
521
|
+
"description": "左右滑动时指定的高度坐标(像素,0-屏幕高度)"
|
|
522
|
+
},
|
|
523
|
+
"y_percent": {
|
|
524
|
+
"type": "number",
|
|
525
|
+
"description": "左右滑动时指定的高度百分比 (0-100)"
|
|
456
526
|
}
|
|
457
527
|
},
|
|
458
528
|
"required": ["direction"]
|
|
@@ -845,7 +915,11 @@ class MobileMCPServer:
|
|
|
845
915
|
|
|
846
916
|
# 导航
|
|
847
917
|
elif name == "mobile_swipe":
|
|
848
|
-
result = await self.tools.swipe(
|
|
918
|
+
result = await self.tools.swipe(
|
|
919
|
+
arguments["direction"],
|
|
920
|
+
y=arguments.get("y"),
|
|
921
|
+
y_percent=arguments.get("y_percent")
|
|
922
|
+
)
|
|
849
923
|
return [TextContent(type="text", text=self.format_response(result))]
|
|
850
924
|
|
|
851
925
|
elif name == "mobile_press_key":
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mobile-mcp-ai
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.5
|
|
4
4
|
Summary: 移动端自动化 MCP Server - 支持 Android/iOS,AI 功能可选(基础工具不需要 AI)
|
|
5
5
|
Home-page: https://github.com/test111ddff-hash/mobile-mcp-ai
|
|
6
6
|
Author: douzi
|
|
@@ -408,10 +408,18 @@ tidevice list
|
|
|
408
408
|
|:---:|------|------|
|
|
409
409
|
| 📋 | `mobile_list_elements` | 列出页面元素 |
|
|
410
410
|
| 📸 | `mobile_take_screenshot` | 截图 |
|
|
411
|
+
| 📸 | `mobile_screenshot_with_som` | Set-of-Mark 截图(智能标注) |
|
|
412
|
+
| 📸 | `mobile_screenshot_with_grid` | 带网格坐标的截图 |
|
|
411
413
|
| 📐 | `mobile_get_screen_size` | 屏幕尺寸 |
|
|
412
414
|
| 👆 | `mobile_click_by_text` | 文本点击 |
|
|
413
415
|
| 👆 | `mobile_click_by_id` | ID 点击 |
|
|
414
416
|
| 👆 | `mobile_click_at_coords` | 坐标点击 |
|
|
417
|
+
| 👆 | `mobile_click_by_percent` | 百分比点击 |
|
|
418
|
+
| 👆 | `mobile_click_by_som` | SoM 编号点击 |
|
|
419
|
+
| 👆 | `mobile_long_press_by_id` | ID 长按 |
|
|
420
|
+
| 👆 | `mobile_long_press_by_text` | 文本长按 |
|
|
421
|
+
| 👆 | `mobile_long_press_by_percent` | 百分比长按 |
|
|
422
|
+
| 👆 | `mobile_long_press_at_coords` | 坐标长按 |
|
|
415
423
|
| ⌨️ | `mobile_input_text_by_id` | ID 输入 |
|
|
416
424
|
| ⌨️ | `mobile_input_at_coords` | 坐标输入 |
|
|
417
425
|
| 👆 | `mobile_swipe` | 滑动 |
|
|
@@ -422,6 +430,11 @@ tidevice list
|
|
|
422
430
|
| 📦 | `mobile_list_apps` | 列出应用 |
|
|
423
431
|
| 📱 | `mobile_list_devices` | 列出设备 |
|
|
424
432
|
| 🔌 | `mobile_check_connection` | 检查连接 |
|
|
433
|
+
| 🔍 | `mobile_find_close_button` | 查找关闭按钮 |
|
|
434
|
+
| 🚫 | `mobile_close_popup` | 关闭弹窗 |
|
|
435
|
+
| 🚫 | `mobile_close_ad` | 智能关闭广告弹窗 |
|
|
436
|
+
| 🎯 | `mobile_template_close` | 模板匹配关闭弹窗 |
|
|
437
|
+
| ➕ | `mobile_template_add` | 添加 X 号模板 |
|
|
425
438
|
| ✅ | `mobile_assert_text` | 断言文本 |
|
|
426
439
|
| 📜 | `mobile_get_operation_history` | 操作历史 |
|
|
427
440
|
| 🗑️ | `mobile_clear_operation_history` | 清空历史 |
|
|
@@ -65,16 +65,6 @@ templates/close_buttons/auto_x_0112_152840.png
|
|
|
65
65
|
templates/close_buttons/auto_x_0112_153256.png
|
|
66
66
|
templates/close_buttons/auto_x_0112_154847.png
|
|
67
67
|
templates/close_buttons/gray_x_stock_ad.png
|
|
68
|
-
tests/test_mind_cloud_my_space.py
|
|
69
|
-
tests/test_mind_correct.py
|
|
70
|
-
tests/test_mind_improved.py
|
|
71
|
-
tests/test_mind_optimized.py
|
|
72
|
-
tests/test_open_mind.py
|
|
73
|
-
tests/test_priority_demo.py
|
|
74
|
-
tests/test_simple.py
|
|
75
|
-
tests/test_举报.py
|
|
76
|
-
tests/test_切换语言到English.py
|
|
77
|
-
tests/test_测试.py
|
|
78
68
|
utils/__init__.py
|
|
79
69
|
utils/logger.py
|
|
80
70
|
utils/xml_formatter.py
|
|
@@ -25,7 +25,7 @@ if requirements_file.exists():
|
|
|
25
25
|
|
|
26
26
|
setup(
|
|
27
27
|
name="mobile-mcp-ai",
|
|
28
|
-
version="2.5.
|
|
28
|
+
version="2.5.5", # 支持左右滑动指定高度坐标和百分比
|
|
29
29
|
author="douzi",
|
|
30
30
|
author_email="1492994674@qq.com",
|
|
31
31
|
description="移动端自动化 MCP Server - 支持 Android/iOS,AI 功能可选(基础工具不需要 AI)",
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
"""
|
|
4
|
-
测试用例: Mind云文档我的空间
|
|
5
|
-
生成时间: 2025-12-17 11:00:00
|
|
6
|
-
"""
|
|
7
|
-
import time
|
|
8
|
-
import uiautomator2 as u2
|
|
9
|
-
|
|
10
|
-
PACKAGE_NAME = "com.im30.mind"
|
|
11
|
-
|
|
12
|
-
# 广告关闭按钮关键词(可自定义)
|
|
13
|
-
AD_CLOSE_KEYWORDS = ['关闭', '跳过', 'Skip', 'Close', '×', 'X', '我知道了', '稍后再说']
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def smart_wait(d, timeout=10):
|
|
17
|
-
"""智能等待页面稳定"""
|
|
18
|
-
d.implicitly_wait(timeout)
|
|
19
|
-
time.sleep(0.5) # 额外等待动画
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def close_ad_if_exists(d):
|
|
23
|
-
"""尝试关闭广告弹窗"""
|
|
24
|
-
for keyword in AD_CLOSE_KEYWORDS:
|
|
25
|
-
elem = d(textContains=keyword)
|
|
26
|
-
if elem.exists(timeout=0.5):
|
|
27
|
-
try:
|
|
28
|
-
elem.click()
|
|
29
|
-
print(f' 📢 关闭广告: {keyword}')
|
|
30
|
-
time.sleep(0.5)
|
|
31
|
-
return True
|
|
32
|
-
except:
|
|
33
|
-
pass
|
|
34
|
-
return False
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def safe_click(d, selector, timeout=5):
|
|
38
|
-
"""安全点击(带等待和重试)"""
|
|
39
|
-
try:
|
|
40
|
-
if selector.exists(timeout=timeout):
|
|
41
|
-
selector.click()
|
|
42
|
-
return True
|
|
43
|
-
return False
|
|
44
|
-
except Exception as e:
|
|
45
|
-
print(f' ⚠️ 点击失败: {e}')
|
|
46
|
-
return False
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def test_main():
|
|
50
|
-
# 连接设备
|
|
51
|
-
d = u2.connect()
|
|
52
|
-
d.implicitly_wait(10) # 设置全局等待
|
|
53
|
-
|
|
54
|
-
# 启动应用
|
|
55
|
-
d.app_start(PACKAGE_NAME)
|
|
56
|
-
smart_wait(d)
|
|
57
|
-
|
|
58
|
-
# 尝试关闭启动广告
|
|
59
|
-
close_ad_if_exists(d)
|
|
60
|
-
|
|
61
|
-
# 步骤1: 点击文本 'Mind'
|
|
62
|
-
safe_click(d, d(text='Mind'))
|
|
63
|
-
smart_wait(d)
|
|
64
|
-
close_ad_if_exists(d) # 检查广告
|
|
65
|
-
|
|
66
|
-
# 步骤2: 点击坐标 (756, 2277)
|
|
67
|
-
d.click(756, 2277)
|
|
68
|
-
smart_wait(d)
|
|
69
|
-
close_ad_if_exists(d) # 检查广告
|
|
70
|
-
|
|
71
|
-
# 步骤3: 点击坐标 (815, 285)
|
|
72
|
-
d.click(815, 285)
|
|
73
|
-
smart_wait(d)
|
|
74
|
-
close_ad_if_exists(d) # 检查广告
|
|
75
|
-
|
|
76
|
-
print('✅ 测试完成')
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if __name__ == '__main__':
|
|
80
|
-
test_main()
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
"""
|
|
4
|
-
测试用例: Mind云文档我的空间_正确版
|
|
5
|
-
生成时间: 2025-12-17 11:08:10
|
|
6
|
-
"""
|
|
7
|
-
import time
|
|
8
|
-
import uiautomator2 as u2
|
|
9
|
-
|
|
10
|
-
PACKAGE_NAME = "com.im30.mind"
|
|
11
|
-
|
|
12
|
-
# 广告关闭按钮关键词(可自定义)
|
|
13
|
-
AD_CLOSE_KEYWORDS = ['关闭', '跳过', 'Skip', 'Close', '×', 'X', '我知道了', '稍后再说']
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def smart_wait(d, seconds=1):
|
|
17
|
-
"""等待页面稳定"""
|
|
18
|
-
time.sleep(seconds)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def close_ad_if_exists(d, quick=False):
|
|
22
|
-
"""尝试关闭广告弹窗(quick=True 时只检查常见的)"""
|
|
23
|
-
keywords = AD_CLOSE_KEYWORDS[:3] if quick else AD_CLOSE_KEYWORDS
|
|
24
|
-
for keyword in keywords:
|
|
25
|
-
elem = d(textContains=keyword)
|
|
26
|
-
if elem.exists(timeout=0.3): # 缩短超时
|
|
27
|
-
try:
|
|
28
|
-
elem.click()
|
|
29
|
-
print(f' 📢 关闭广告: {keyword}')
|
|
30
|
-
time.sleep(0.3)
|
|
31
|
-
return True
|
|
32
|
-
except:
|
|
33
|
-
pass
|
|
34
|
-
return False
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def safe_click(d, selector, timeout=3):
|
|
38
|
-
"""安全点击(带等待)"""
|
|
39
|
-
try:
|
|
40
|
-
if selector.exists(timeout=timeout):
|
|
41
|
-
selector.click()
|
|
42
|
-
return True
|
|
43
|
-
return False
|
|
44
|
-
except Exception as e:
|
|
45
|
-
print(f' ⚠️ 点击失败: {e}')
|
|
46
|
-
return False
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def test_main():
|
|
50
|
-
# 连接设备
|
|
51
|
-
d = u2.connect()
|
|
52
|
-
d.implicitly_wait(10) # 设置全局等待
|
|
53
|
-
|
|
54
|
-
# 启动应用
|
|
55
|
-
d.app_start(PACKAGE_NAME)
|
|
56
|
-
smart_wait(d)
|
|
57
|
-
|
|
58
|
-
# 尝试关闭启动广告
|
|
59
|
-
close_ad_if_exists(d)
|
|
60
|
-
|
|
61
|
-
# 步骤1: 点击坐标 (756, 2277)
|
|
62
|
-
d.click(756, 2277)
|
|
63
|
-
time.sleep(0.5) # 等待响应
|
|
64
|
-
|
|
65
|
-
# 步骤2: 点击坐标 (815, 285)
|
|
66
|
-
d.click(815, 285)
|
|
67
|
-
time.sleep(0.5) # 等待响应
|
|
68
|
-
|
|
69
|
-
print('✅ 测试完成')
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if __name__ == '__main__':
|
|
73
|
-
test_main()
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
"""
|
|
4
|
-
测试用例: 打开Mind应用测试
|
|
5
|
-
生成时间: 2025-12-17 10:52:37
|
|
6
|
-
"""
|
|
7
|
-
import time
|
|
8
|
-
import uiautomator2 as u2
|
|
9
|
-
|
|
10
|
-
PACKAGE_NAME = "com.im30.mind"
|
|
11
|
-
|
|
12
|
-
# 广告关闭按钮关键词(可自定义)
|
|
13
|
-
AD_CLOSE_KEYWORDS = ['关闭', '跳过', 'Skip', 'Close', '×', 'X', '我知道了', '稍后再说']
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def smart_wait(d, timeout=10):
|
|
17
|
-
"""智能等待页面稳定"""
|
|
18
|
-
d.implicitly_wait(timeout)
|
|
19
|
-
time.sleep(0.5) # 额外等待动画
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def close_ad_if_exists(d):
|
|
23
|
-
"""尝试关闭广告弹窗"""
|
|
24
|
-
for keyword in AD_CLOSE_KEYWORDS:
|
|
25
|
-
elem = d(textContains=keyword)
|
|
26
|
-
if elem.exists(timeout=0.5):
|
|
27
|
-
try:
|
|
28
|
-
elem.click()
|
|
29
|
-
print(f' 📢 关闭广告: {keyword}')
|
|
30
|
-
time.sleep(0.5)
|
|
31
|
-
return True
|
|
32
|
-
except:
|
|
33
|
-
pass
|
|
34
|
-
return False
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def safe_click(d, selector, timeout=5):
|
|
38
|
-
"""安全点击(带等待和重试)"""
|
|
39
|
-
try:
|
|
40
|
-
if selector.exists(timeout=timeout):
|
|
41
|
-
selector.click()
|
|
42
|
-
return True
|
|
43
|
-
return False
|
|
44
|
-
except Exception as e:
|
|
45
|
-
print(f' ⚠️ 点击失败: {e}')
|
|
46
|
-
return False
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def test_main():
|
|
50
|
-
# 连接设备
|
|
51
|
-
d = u2.connect()
|
|
52
|
-
d.implicitly_wait(10) # 设置全局等待
|
|
53
|
-
|
|
54
|
-
# 启动应用
|
|
55
|
-
d.app_start(PACKAGE_NAME)
|
|
56
|
-
smart_wait(d)
|
|
57
|
-
|
|
58
|
-
# 尝试关闭启动广告
|
|
59
|
-
close_ad_if_exists(d)
|
|
60
|
-
|
|
61
|
-
# 步骤1: 点击文本 'Mind'
|
|
62
|
-
safe_click(d, d(text='Mind'))
|
|
63
|
-
smart_wait(d)
|
|
64
|
-
close_ad_if_exists(d) # 检查广告
|
|
65
|
-
|
|
66
|
-
# 步骤2: 点击坐标 (540, 1200)
|
|
67
|
-
d.click(540, 1200)
|
|
68
|
-
smart_wait(d)
|
|
69
|
-
close_ad_if_exists(d) # 检查广告
|
|
70
|
-
|
|
71
|
-
# 步骤3: 输入文本 '测试'
|
|
72
|
-
d(resourceId='com.im30.mind:id/search').set_text('测试')
|
|
73
|
-
smart_wait(d)
|
|
74
|
-
|
|
75
|
-
# 步骤4: 滑动 up
|
|
76
|
-
d.swipe_ext('up')
|
|
77
|
-
smart_wait(d)
|
|
78
|
-
|
|
79
|
-
print('✅ 测试完成')
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if __name__ == '__main__':
|
|
83
|
-
test_main()
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
"""
|
|
4
|
-
测试用例: Mind云文档我的空间_优化版
|
|
5
|
-
生成时间: 2025-12-17 11:04:53
|
|
6
|
-
"""
|
|
7
|
-
import time
|
|
8
|
-
import uiautomator2 as u2
|
|
9
|
-
|
|
10
|
-
PACKAGE_NAME = "com.im30.mind"
|
|
11
|
-
|
|
12
|
-
# 广告关闭按钮关键词(可自定义)
|
|
13
|
-
AD_CLOSE_KEYWORDS = ['关闭', '跳过', 'Skip', 'Close', '×', 'X', '我知道了', '稍后再说']
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def smart_wait(d, seconds=1):
|
|
17
|
-
"""等待页面稳定"""
|
|
18
|
-
time.sleep(seconds)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def close_ad_if_exists(d, quick=False):
|
|
22
|
-
"""尝试关闭广告弹窗(quick=True 时只检查常见的)"""
|
|
23
|
-
keywords = AD_CLOSE_KEYWORDS[:3] if quick else AD_CLOSE_KEYWORDS
|
|
24
|
-
for keyword in keywords:
|
|
25
|
-
elem = d(textContains=keyword)
|
|
26
|
-
if elem.exists(timeout=0.3): # 缩短超时
|
|
27
|
-
try:
|
|
28
|
-
elem.click()
|
|
29
|
-
print(f' 📢 关闭广告: {keyword}')
|
|
30
|
-
time.sleep(0.3)
|
|
31
|
-
return True
|
|
32
|
-
except:
|
|
33
|
-
pass
|
|
34
|
-
return False
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def safe_click(d, selector, timeout=3):
|
|
38
|
-
"""安全点击(带等待)"""
|
|
39
|
-
try:
|
|
40
|
-
if selector.exists(timeout=timeout):
|
|
41
|
-
selector.click()
|
|
42
|
-
return True
|
|
43
|
-
return False
|
|
44
|
-
except Exception as e:
|
|
45
|
-
print(f' ⚠️ 点击失败: {e}')
|
|
46
|
-
return False
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def test_main():
|
|
50
|
-
# 连接设备
|
|
51
|
-
d = u2.connect()
|
|
52
|
-
d.implicitly_wait(10) # 设置全局等待
|
|
53
|
-
|
|
54
|
-
# 启动应用
|
|
55
|
-
d.app_start(PACKAGE_NAME)
|
|
56
|
-
smart_wait(d)
|
|
57
|
-
|
|
58
|
-
# 尝试关闭启动广告
|
|
59
|
-
close_ad_if_exists(d)
|
|
60
|
-
|
|
61
|
-
# 步骤1: 点击文本 'Mind'
|
|
62
|
-
safe_click(d, d(text='Mind'))
|
|
63
|
-
time.sleep(0.5) # 等待响应
|
|
64
|
-
|
|
65
|
-
# 步骤2: 点击坐标 (756, 2277)
|
|
66
|
-
d.click(756, 2277)
|
|
67
|
-
time.sleep(0.5) # 等待响应
|
|
68
|
-
|
|
69
|
-
# 步骤3: 点击坐标 (815, 285)
|
|
70
|
-
d.click(815, 285)
|
|
71
|
-
time.sleep(0.5) # 等待响应
|
|
72
|
-
|
|
73
|
-
print('✅ 测试完成')
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if __name__ == '__main__':
|
|
77
|
-
test_main()
|