mobile-mcp-ai 2.6.12__py3-none-any.whl → 2.7.3__py3-none-any.whl
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/core/basic_tools_lite.py +571 -52
- mobile_mcp/mcp_tools/mcp_server.py +111 -104
- {mobile_mcp_ai-2.6.12.dist-info → mobile_mcp_ai-2.7.3.dist-info}/METADATA +47 -19
- {mobile_mcp_ai-2.6.12.dist-info → mobile_mcp_ai-2.7.3.dist-info}/RECORD +8 -9
- {mobile_mcp_ai-2.6.12.dist-info → mobile_mcp_ai-2.7.3.dist-info}/WHEEL +1 -1
- mobile_mcp/core/tool_selection_helper.py +0 -168
- {mobile_mcp_ai-2.6.12.dist-info → mobile_mcp_ai-2.7.3.dist-info}/entry_points.txt +0 -0
- {mobile_mcp_ai-2.6.12.dist-info → mobile_mcp_ai-2.7.3.dist-info}/licenses/LICENSE +0 -0
- {mobile_mcp_ai-2.6.12.dist-info → mobile_mcp_ai-2.7.3.dist-info}/top_level.txt +0 -0
|
@@ -226,36 +226,7 @@ class MobileMCPServer:
|
|
|
226
226
|
|
|
227
227
|
tools.append(Tool(
|
|
228
228
|
name="mobile_list_elements",
|
|
229
|
-
description=
|
|
230
|
-
|
|
231
|
-
⚠️ 【核心原则】优先使用控件树定位,截图仅作为兜底方案
|
|
232
|
-
|
|
233
|
-
🎯 使用场景(优先使用):
|
|
234
|
-
1. ✅ 首次进入页面时:获取所有可交互元素
|
|
235
|
-
2. ✅ 查找特定元素时:检查元素是否存在
|
|
236
|
-
3. ✅ 验证操作结果时:确认元素是否出现/消失
|
|
237
|
-
4. ✅ 需要精确点击时:获取元素的 resource_id 或 text
|
|
238
|
-
|
|
239
|
-
📌 控件树定位优势:
|
|
240
|
-
- ⚡ 速度快:直接获取 XML,无需截图和图像处理(快 5-20 倍)
|
|
241
|
-
- 🎯 准确:获取完整的元素属性(resource_id、text、bounds、clickable 等)
|
|
242
|
-
- 💾 高效:不生成图片文件,节省存储和传输
|
|
243
|
-
- 🔄 可复用:一次获取的元素列表可用于多次操作
|
|
244
|
-
|
|
245
|
-
⚡ 推荐流程:
|
|
246
|
-
1. 调用 mobile_list_elements() 获取元素列表
|
|
247
|
-
2. 从元素列表中找到目标元素(通过 text 或 resource_id)
|
|
248
|
-
3. 使用 mobile_click_by_text() 或 mobile_click_by_id() 点击
|
|
249
|
-
4. 仅在控件树找不到元素时,才使用 mobile_screenshot_with_som()
|
|
250
|
-
|
|
251
|
-
❌ 错误用法:
|
|
252
|
-
- 每次都先截图,不先调用 list_elements
|
|
253
|
-
- 重复调用 list_elements,不复用已获取的元素列表
|
|
254
|
-
|
|
255
|
-
✅ 正确用法:
|
|
256
|
-
elements = mobile_list_elements() # 一次获取
|
|
257
|
-
mobile_click_by_text("设置") # 直接点击
|
|
258
|
-
mobile_click_by_text("退出登录") # 复用元素列表""",
|
|
229
|
+
description=desc_list_elements,
|
|
259
230
|
inputSchema={"type": "object", "properties": {}, "required": []}
|
|
260
231
|
))
|
|
261
232
|
|
|
@@ -309,49 +280,8 @@ mobile_click_by_text("退出登录") # 复用元素列表""",
|
|
|
309
280
|
|
|
310
281
|
tools.append(Tool(
|
|
311
282
|
name="mobile_screenshot_with_som",
|
|
312
|
-
description=
|
|
313
|
-
|
|
314
|
-
⚠️ 【重要】仅在控件树定位失败时使用!
|
|
315
|
-
|
|
316
|
-
🎯 使用场景(兜底方案):
|
|
317
|
-
1. ⚠️ 控件树定位失败:
|
|
318
|
-
- mobile_list_elements() 返回空或找不到目标元素
|
|
319
|
-
- 元素不在控件树中(如游戏、Unity 应用)
|
|
320
|
-
2. ✅ 需要视觉确认:
|
|
321
|
-
- 首次进入新页面,需要了解整体布局
|
|
322
|
-
- 操作后需要视觉确认页面变化
|
|
323
|
-
- 需要给用户展示页面状态
|
|
324
|
-
|
|
325
|
-
⚡ 推荐流程(优先使用控件树):
|
|
326
|
-
1. 先调用 mobile_list_elements() 获取元素列表
|
|
327
|
-
2. 如果找到目标元素 → 使用 mobile_click_by_text() 或 mobile_click_by_id()
|
|
328
|
-
3. 如果找不到 → 才调用 mobile_screenshot_with_som()
|
|
329
|
-
4. 从截图分析找到元素编号 → 使用 mobile_click_by_som(编号)
|
|
330
|
-
|
|
331
|
-
❌ 错误用法:
|
|
332
|
-
- 每次都先截图,不先调用 list_elements
|
|
333
|
-
- 在控件树能找到元素时也使用截图
|
|
334
|
-
|
|
335
|
-
✅ 正确用法:
|
|
336
|
-
elements = mobile_list_elements() # 先尝试控件树
|
|
337
|
-
if not find_target(elements):
|
|
338
|
-
screenshot = mobile_screenshot_with_som() # 控件树失败才截图
|
|
339
|
-
mobile_click_by_som(index)
|
|
340
|
-
|
|
341
|
-
💡 弹窗检测:
|
|
342
|
-
- check_popup=True: 明确弹窗场景时使用(如调用 mobile_close_popup 前)
|
|
343
|
-
- check_popup=False: 普通截图,不检测弹窗(默认)""",
|
|
344
|
-
inputSchema={
|
|
345
|
-
"type": "object",
|
|
346
|
-
"properties": {
|
|
347
|
-
"check_popup": {
|
|
348
|
-
"type": "boolean",
|
|
349
|
-
"description": "是否检测弹窗,默认 False。仅在明确弹窗场景时设置为 True",
|
|
350
|
-
"default": False
|
|
351
|
-
}
|
|
352
|
-
},
|
|
353
|
-
"required": []
|
|
354
|
-
}
|
|
283
|
+
description=desc_som,
|
|
284
|
+
inputSchema={"type": "object", "properties": {}, "required": []}
|
|
355
285
|
))
|
|
356
286
|
|
|
357
287
|
tools.append(Tool(
|
|
@@ -397,31 +327,7 @@ if not find_target(elements):
|
|
|
397
327
|
|
|
398
328
|
tools.append(Tool(
|
|
399
329
|
name="mobile_click_by_text",
|
|
400
|
-
description=
|
|
401
|
-
|
|
402
|
-
✅ 最稳定的定位方式,跨设备兼容
|
|
403
|
-
✅ 实时检测元素是否存在,元素不存在会报错
|
|
404
|
-
✅ 不会误点击到其他位置
|
|
405
|
-
|
|
406
|
-
⚡ 使用流程(必须):
|
|
407
|
-
1. 先调用 mobile_list_elements() 获取元素列表
|
|
408
|
-
2. 从元素列表中找到目标元素的 text
|
|
409
|
-
3. 调用 mobile_click_by_text("文本") 点击
|
|
410
|
-
|
|
411
|
-
💡 定位优先级:文本 > ID > 百分比 > 坐标
|
|
412
|
-
|
|
413
|
-
📍 当页面有多个相同文案时,可使用 position 参数指定位置:
|
|
414
|
-
- 垂直方向: "top"/"upper"/"上", "bottom"/"lower"/"下", "middle"/"center"/"中"
|
|
415
|
-
- 水平方向: "left"/"左", "right"/"右", "center"/"中"
|
|
416
|
-
例如:点击"底部"的"微剧"tab,使用 position="bottom"
|
|
417
|
-
|
|
418
|
-
❌ 错误用法:
|
|
419
|
-
- 不先调用 list_elements,直接猜测文本点击
|
|
420
|
-
- 在控件树能找到元素时使用截图+坐标点击
|
|
421
|
-
|
|
422
|
-
✅ 正确用法:
|
|
423
|
-
elements = mobile_list_elements() # 先获取元素列表
|
|
424
|
-
mobile_click_by_text("设置") # 从元素列表中找到后直接点击""",
|
|
330
|
+
description=desc_click_text,
|
|
425
331
|
inputSchema={
|
|
426
332
|
"type": "object",
|
|
427
333
|
"properties": {
|
|
@@ -588,18 +494,82 @@ mobile_click_by_text("设置") # 从元素列表中找到后直接点击"
|
|
|
588
494
|
# ==================== 导航操作 ====================
|
|
589
495
|
tools.append(Tool(
|
|
590
496
|
name="mobile_swipe",
|
|
591
|
-
description="👆
|
|
497
|
+
description="👆 滑动屏幕。方向:up/down/left/right\n\n"
|
|
498
|
+
"🎯 适用场景:\n"
|
|
499
|
+
"- 滑动页面(列表、页面切换)\n"
|
|
500
|
+
"- 拖动进度条/滑块(SeekBar、ProgressBar)\n"
|
|
501
|
+
"- 滑动选择器(Picker、Slider)\n\n"
|
|
502
|
+
"💡 左右滑动时,可指定高度坐标或百分比:\n"
|
|
503
|
+
"- y: 指定高度坐标(像素)\n"
|
|
504
|
+
"- y_percent: 指定高度百分比 (0-100)\n"
|
|
505
|
+
"- 两者都未指定时,使用屏幕中心高度\n"
|
|
506
|
+
"- 📌 拖动进度条时,使用进度条的 Y 位置(百分比或像素)\n\n"
|
|
507
|
+
"💡 横向滑动(left/right)时,可指定滑动距离:\n"
|
|
508
|
+
"- distance: 滑动距离(像素)\n"
|
|
509
|
+
"- distance_percent: 滑动距离百分比 (0-100)\n"
|
|
510
|
+
"- 两者都未指定时,使用默认距离(屏幕宽度的 60%)\n"
|
|
511
|
+
"- 📌 拖动进度条时,distance_percent 控制拖动幅度\n\n"
|
|
512
|
+
"💡 拖动进度条示例:\n"
|
|
513
|
+
"- 倒退:direction='left', y_percent=91(进度条位置), distance_percent=30\n"
|
|
514
|
+
"- 前进:direction='right', y_percent=91, distance_percent=30\n\n"
|
|
515
|
+
"⚠️ **推荐使用 mobile_drag_progress_bar 拖动进度条**(自动检测进度条位置,无需手动指定)",
|
|
592
516
|
inputSchema={
|
|
593
517
|
"type": "object",
|
|
594
518
|
"properties": {
|
|
595
519
|
"direction": {"type": "string", "enum": ["up", "down", "left", "right"], "description": "方向"},
|
|
596
|
-
"y": {"type": "integer", "description": "
|
|
597
|
-
"y_percent": {"type": "number", "description": "
|
|
520
|
+
"y": {"type": "integer", "description": "左右滑动时指定的高度坐标(像素)"},
|
|
521
|
+
"y_percent": {"type": "number", "description": "左右滑动时指定的高度百分比 (0-100)"},
|
|
522
|
+
"distance": {"type": "integer", "description": "横向滑动时指定的滑动距离(像素),仅用于 left/right"},
|
|
523
|
+
"distance_percent": {"type": "number", "description": "横向滑动时指定的滑动距离百分比 (0-100),仅用于 left/right"}
|
|
598
524
|
},
|
|
599
525
|
"required": ["direction"]
|
|
600
526
|
}
|
|
601
527
|
))
|
|
602
528
|
|
|
529
|
+
tools.append(Tool(
|
|
530
|
+
name="mobile_drag_progress_bar",
|
|
531
|
+
description="🎯 智能拖动进度条(⭐⭐ 推荐用于拖动视频/音频进度条)\n\n"
|
|
532
|
+
"✅ **自动检测进度条是否可见**:\n"
|
|
533
|
+
"- 如果进度条已显示,直接拖动(无需先点击播放区域)\n"
|
|
534
|
+
"- 如果进度条未显示,自动点击播放区域显示控制栏,再拖动\n\n"
|
|
535
|
+
"🎯 优势:\n"
|
|
536
|
+
"- 自动检测进度条位置,无需手动指定 y_percent\n"
|
|
537
|
+
"- 智能判断是否需要显示控制栏\n"
|
|
538
|
+
"- 使用 swipe 拖动,更稳定可靠\n\n"
|
|
539
|
+
"💡 参数说明:\n"
|
|
540
|
+
"- direction: 'left'(倒退)或 'right'(前进),默认 'right'\n"
|
|
541
|
+
"- distance_percent: 拖动距离百分比 (0-100),默认 30%\n"
|
|
542
|
+
"- y_percent: 进度条位置(可选,未指定则自动检测)\n"
|
|
543
|
+
"- y: 进度条位置坐标(可选,未指定则自动检测)\n\n"
|
|
544
|
+
"📋 使用示例:\n"
|
|
545
|
+
"- 前进30%:mobile_drag_progress_bar(direction='right', distance_percent=30)\n"
|
|
546
|
+
"- 倒退30%:mobile_drag_progress_bar(direction='left', distance_percent=30)\n"
|
|
547
|
+
"- 前进到指定位置:先点击进度条位置,或使用 mobile_swipe",
|
|
548
|
+
inputSchema={
|
|
549
|
+
"type": "object",
|
|
550
|
+
"properties": {
|
|
551
|
+
"direction": {
|
|
552
|
+
"type": "string",
|
|
553
|
+
"enum": ["left", "right"],
|
|
554
|
+
"description": "拖动方向:'left'(倒退)或 'right'(前进),默认 'right'"
|
|
555
|
+
},
|
|
556
|
+
"distance_percent": {
|
|
557
|
+
"type": "number",
|
|
558
|
+
"description": "拖动距离百分比 (0-100),默认 30%"
|
|
559
|
+
},
|
|
560
|
+
"y_percent": {
|
|
561
|
+
"type": "number",
|
|
562
|
+
"description": "进度条的垂直位置百分比 (0-100),可选,未指定则自动检测"
|
|
563
|
+
},
|
|
564
|
+
"y": {
|
|
565
|
+
"type": "integer",
|
|
566
|
+
"description": "进度条的垂直位置坐标(像素),可选,未指定则自动检测"
|
|
567
|
+
}
|
|
568
|
+
},
|
|
569
|
+
"required": []
|
|
570
|
+
}
|
|
571
|
+
))
|
|
572
|
+
|
|
603
573
|
tools.append(Tool(
|
|
604
574
|
name="mobile_press_key",
|
|
605
575
|
description="⌨️ 按键:home/back/enter/search。",
|
|
@@ -715,6 +685,8 @@ mobile_click_by_text("设置") # 从元素列表中找到后直接点击"
|
|
|
715
685
|
- 如果没有弹窗 → 直接返回"无弹窗",不执行任何操作
|
|
716
686
|
- 如果有弹窗 → 自动查找并点击关闭按钮
|
|
717
687
|
|
|
688
|
+
💡 【优化】如果已通过list_elements识别到弹窗,可传入popup_detected=true跳过重复检测
|
|
689
|
+
|
|
718
690
|
✅ 适用场景:
|
|
719
691
|
- 启动应用后检测并关闭可能出现的弹窗
|
|
720
692
|
- 页面跳转后检测并关闭弹窗
|
|
@@ -730,7 +702,21 @@ mobile_click_by_text("设置") # 从元素列表中找到后直接点击"
|
|
|
730
702
|
tools.append(Tool(
|
|
731
703
|
name="mobile_close_popup",
|
|
732
704
|
description=desc_close_popup,
|
|
733
|
-
inputSchema={
|
|
705
|
+
inputSchema={
|
|
706
|
+
"type": "object",
|
|
707
|
+
"properties": {
|
|
708
|
+
"popup_detected": {
|
|
709
|
+
"type": "boolean",
|
|
710
|
+
"description": "可选,如果已通过list_elements识别到弹窗,传入true可跳过重复检测"
|
|
711
|
+
},
|
|
712
|
+
"popup_bounds": {
|
|
713
|
+
"type": "array",
|
|
714
|
+
"items": {"type": "number"},
|
|
715
|
+
"description": "可选,弹窗边界[x1, y1, x2, y2],如果已识别到弹窗区域可传入"
|
|
716
|
+
}
|
|
717
|
+
},
|
|
718
|
+
"required": []
|
|
719
|
+
}
|
|
734
720
|
))
|
|
735
721
|
|
|
736
722
|
tools.append(Tool(
|
|
@@ -1018,7 +1004,18 @@ mobile_click_by_text("设置") # 从元素列表中找到后直接点击"
|
|
|
1018
1004
|
result = await self.tools.swipe(
|
|
1019
1005
|
arguments["direction"],
|
|
1020
1006
|
y=arguments.get("y"),
|
|
1021
|
-
y_percent=arguments.get("y_percent")
|
|
1007
|
+
y_percent=arguments.get("y_percent"),
|
|
1008
|
+
distance=arguments.get("distance"),
|
|
1009
|
+
distance_percent=arguments.get("distance_percent")
|
|
1010
|
+
)
|
|
1011
|
+
return [TextContent(type="text", text=self.format_response(result))]
|
|
1012
|
+
|
|
1013
|
+
elif name == "mobile_drag_progress_bar":
|
|
1014
|
+
result = await self.tools.drag_progress_bar(
|
|
1015
|
+
direction=arguments.get("direction", "right"),
|
|
1016
|
+
distance_percent=arguments.get("distance_percent", 30.0),
|
|
1017
|
+
y_percent=arguments.get("y_percent"),
|
|
1018
|
+
y=arguments.get("y")
|
|
1022
1019
|
)
|
|
1023
1020
|
return [TextContent(type="text", text=self.format_response(result))]
|
|
1024
1021
|
|
|
@@ -1062,7 +1059,17 @@ mobile_click_by_text("设置") # 从元素列表中找到后直接点击"
|
|
|
1062
1059
|
return [TextContent(type="text", text=self.format_response(result))]
|
|
1063
1060
|
|
|
1064
1061
|
elif name == "mobile_close_popup":
|
|
1065
|
-
|
|
1062
|
+
popup_detected = arguments.get("popup_detected")
|
|
1063
|
+
popup_bounds = arguments.get("popup_bounds")
|
|
1064
|
+
# 如果传入了popup_bounds,转换为tuple
|
|
1065
|
+
if popup_bounds and isinstance(popup_bounds, list) and len(popup_bounds) == 4:
|
|
1066
|
+
popup_bounds = tuple(popup_bounds)
|
|
1067
|
+
elif popup_bounds:
|
|
1068
|
+
popup_bounds = None # 格式不正确,忽略
|
|
1069
|
+
result = self.tools.close_popup(
|
|
1070
|
+
popup_detected=popup_detected,
|
|
1071
|
+
popup_bounds=popup_bounds
|
|
1072
|
+
)
|
|
1066
1073
|
return [TextContent(type="text", text=self.format_response(result))]
|
|
1067
1074
|
|
|
1068
1075
|
elif name == "mobile_assert_text":
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mobile-mcp-ai
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.7.3
|
|
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
|
|
@@ -46,6 +46,10 @@ Requires-Dist: facebook-wda>=1.4.0; extra == "ios"
|
|
|
46
46
|
Provides-Extra: h5
|
|
47
47
|
Requires-Dist: Appium-Python-Client>=3.0.0; extra == "h5"
|
|
48
48
|
Requires-Dist: selenium>=4.0.0; extra == "h5"
|
|
49
|
+
Provides-Extra: windows
|
|
50
|
+
Requires-Dist: pyautogui>=0.9.0; extra == "windows"
|
|
51
|
+
Requires-Dist: pyperclip>=1.8.0; extra == "windows"
|
|
52
|
+
Requires-Dist: pygetwindow>=0.0.9; extra == "windows"
|
|
49
53
|
Provides-Extra: all
|
|
50
54
|
Requires-Dist: dashscope>=1.10.0; extra == "all"
|
|
51
55
|
Requires-Dist: openai>=1.0.0; extra == "all"
|
|
@@ -55,6 +59,9 @@ Requires-Dist: selenium>=4.0.0; extra == "all"
|
|
|
55
59
|
Requires-Dist: pytest>=8.0.0; extra == "all"
|
|
56
60
|
Requires-Dist: pytest-asyncio>=0.21.0; extra == "all"
|
|
57
61
|
Requires-Dist: allure-pytest>=2.13.0; extra == "all"
|
|
62
|
+
Requires-Dist: pyautogui>=0.9.0; extra == "all"
|
|
63
|
+
Requires-Dist: pyperclip>=1.8.0; extra == "all"
|
|
64
|
+
Requires-Dist: pygetwindow>=0.0.9; extra == "all"
|
|
58
65
|
Dynamic: author
|
|
59
66
|
Dynamic: author-email
|
|
60
67
|
Dynamic: classifier
|
|
@@ -336,6 +343,34 @@ tidevice list
|
|
|
336
343
|
|
|
337
344
|
保存后**重启 Cursor**。
|
|
338
345
|
|
|
346
|
+
### 批量执行用例(飞书集成)
|
|
347
|
+
|
|
348
|
+
如果你需要从飞书多维表格批量执行用例,`mobile_open_new_chat` 功能会自动打开新会话继续执行。
|
|
349
|
+
|
|
350
|
+
**macOS 用户:** 需要开启辅助功能权限
|
|
351
|
+
|
|
352
|
+
| 步骤 | 操作 |
|
|
353
|
+
|:---:|------|
|
|
354
|
+
| 1 | 打开「系统设置」 |
|
|
355
|
+
| 2 | 点击「隐私与安全性」 |
|
|
356
|
+
| 3 | 点击「辅助功能」 |
|
|
357
|
+
| 4 | 点击 + 号,添加 **Cursor.app** |
|
|
358
|
+
| 5 | 确保开关已打开 ✅ |
|
|
359
|
+
|
|
360
|
+
> ⚠️ 没有此权限,无法自动打开新会话继续执行
|
|
361
|
+
|
|
362
|
+
**Windows 用户:** 需要安装额外依赖
|
|
363
|
+
|
|
364
|
+
```bash
|
|
365
|
+
pip install mobile-mcp-ai[windows]
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
或手动安装:
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
pip install pyautogui pyperclip pygetwindow
|
|
372
|
+
```
|
|
373
|
+
|
|
339
374
|
---
|
|
340
375
|
|
|
341
376
|
## 🚀 使用示例
|
|
@@ -404,24 +439,17 @@ tidevice list
|
|
|
404
439
|
|
|
405
440
|
## 🛠️ 工具列表
|
|
406
441
|
|
|
407
|
-
| 类别 | 工具 | 说明 |
|
|
408
|
-
|
|
409
|
-
| 📋 | `mobile_list_elements` | 列出页面元素 |
|
|
410
|
-
|
|
|
411
|
-
|
|
|
412
|
-
| 📸 | `
|
|
413
|
-
|
|
|
414
|
-
|
|
|
415
|
-
| 👆 | `
|
|
416
|
-
| 👆 | `
|
|
417
|
-
| 👆 | `
|
|
418
|
-
| 📐 | `mobile_get_screen_size` | 屏幕尺寸 | 辅助工具 |
|
|
419
|
-
|
|
420
|
-
> 💡 **工具选择策略**:优先使用控件树定位(`list_elements` + `click_by_text/id`),截图仅作为兜底方案。
|
|
421
|
-
>
|
|
422
|
-
> 📖 详细指南:
|
|
423
|
-
> - [工具选择策略指南](docs/TOOL_SELECTION_STRATEGY.md) - 详细的工具选择策略和决策树
|
|
424
|
-
> - [用例执行最佳实践](docs/EXECUTION_BEST_PRACTICES.md) - 用例执行流程和优化建议
|
|
442
|
+
| 类别 | 工具 | 说明 |
|
|
443
|
+
|:---:|------|------|
|
|
444
|
+
| 📋 | `mobile_list_elements` | 列出页面元素 |
|
|
445
|
+
| 📸 | `mobile_take_screenshot` | 截图 |
|
|
446
|
+
| 📸 | `mobile_screenshot_with_som` | Set-of-Mark 截图(智能标注) |
|
|
447
|
+
| 📸 | `mobile_screenshot_with_grid` | 带网格坐标的截图 |
|
|
448
|
+
| 📐 | `mobile_get_screen_size` | 屏幕尺寸 |
|
|
449
|
+
| 👆 | `mobile_click_by_text` | 文本点击 |
|
|
450
|
+
| 👆 | `mobile_click_by_id` | ID 点击 |
|
|
451
|
+
| 👆 | `mobile_click_at_coords` | 坐标点击 |
|
|
452
|
+
| 👆 | `mobile_click_by_percent` | 百分比点击 |
|
|
425
453
|
| 👆 | `mobile_click_by_som` | SoM 编号点击 |
|
|
426
454
|
| 👆 | `mobile_long_press_by_id` | ID 长按 |
|
|
427
455
|
| 👆 | `mobile_long_press_by_text` | 文本长按 |
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
mobile_mcp/__init__.py,sha256=sQJZTL_sxQFzmcS7jOtS2AHCfUySz40vhX96N6u1qy4,816
|
|
2
2
|
mobile_mcp/config.py,sha256=-xSl9vahp3EFAA97P1ahcnQC-HHAFvccGHpnFAXeKHU,5841
|
|
3
3
|
mobile_mcp/core/__init__.py,sha256=ndMy-cLAIsQDG5op7gM_AIplycqZSZPWEkec1pEhvEY,170
|
|
4
|
-
mobile_mcp/core/basic_tools_lite.py,sha256=
|
|
4
|
+
mobile_mcp/core/basic_tools_lite.py,sha256=DPLPVoi-e-KsYAHANXKlBqCcJPBaE4lV_Qvf-zjL9Ng,219759
|
|
5
5
|
mobile_mcp/core/device_manager.py,sha256=xG5DoeNFs45pl-FTEhEWblqVwxtFK-FmVEGlNL6EqRI,8798
|
|
6
6
|
mobile_mcp/core/dynamic_config.py,sha256=Ja1n1pfb0HspGByqk2_A472mYVniKmGtNEWyjUjmgK8,9811
|
|
7
7
|
mobile_mcp/core/ios_client_wda.py,sha256=Nq9WxevhTWpVpolM-Ymp-b0nUQV3tXLFszmJHbDC4wA,18770
|
|
8
8
|
mobile_mcp/core/ios_device_manager_wda.py,sha256=A44glqI-24un7qST-E3w6BQD8mV92YVUbxy4rLlTScY,11264
|
|
9
9
|
mobile_mcp/core/mobile_client.py,sha256=AaBntQSW2loAw7xL3j_IABNzrJO_Uukf9-F1z1xl6xE,63672
|
|
10
10
|
mobile_mcp/core/template_matcher.py,sha256=tv8RU6zdeDobqphaP4Y8sicb1esg3gcQlZae1tNyitM,14559
|
|
11
|
-
mobile_mcp/core/tool_selection_helper.py,sha256=k5_E4oK_fGg4n6qembvudt6Fw-QXq7_SLJDt4FO3cHc,5764
|
|
12
11
|
mobile_mcp/core/templates/close_buttons/auto_x_0112_151217.png,sha256=s7tBVaYLBApNSEXjwi5kX8GXwUqgbNyNVEhXYjN9nd4,27373
|
|
13
12
|
mobile_mcp/core/templates/close_buttons/auto_x_0112_152037.png,sha256=s7tBVaYLBApNSEXjwi5kX8GXwUqgbNyNVEhXYjN9nd4,27373
|
|
14
13
|
mobile_mcp/core/templates/close_buttons/auto_x_0112_152840.png,sha256=s7tBVaYLBApNSEXjwi5kX8GXwUqgbNyNVEhXYjN9nd4,27373
|
|
@@ -20,14 +19,14 @@ mobile_mcp/core/utils/logger.py,sha256=XXQAHUwT1jc70pq_tYFmL6f_nKrFlYm3hcgl-5RYR
|
|
|
20
19
|
mobile_mcp/core/utils/operation_history_manager.py,sha256=gi8S8HJAMqvkUrY7_-kVbko3Xt7c4GAUziEujRd-N-Y,4792
|
|
21
20
|
mobile_mcp/core/utils/smart_wait.py,sha256=N5wKTUYrNWPruBILqrAjpvtso8Z3GRWCfMIR_aZxPLg,8649
|
|
22
21
|
mobile_mcp/mcp_tools/__init__.py,sha256=xkro8Rwqv_55YlVyhh-3DgRFSsLE3h1r31VIb3bpM6E,143
|
|
23
|
-
mobile_mcp/mcp_tools/mcp_server.py,sha256=
|
|
22
|
+
mobile_mcp/mcp_tools/mcp_server.py,sha256=nEb_chFF_1lN__4nMei2yPzVgzlDeYK67p-KJVta-VU,53521
|
|
24
23
|
mobile_mcp/utils/__init__.py,sha256=8EH0i7UGtx1y_j_GEgdN-cZdWn2sRtZSEOLlNF9HRnY,158
|
|
25
24
|
mobile_mcp/utils/logger.py,sha256=Sqq2Nr0Y4p03erqcrbYKVPCGiFaNGHMcE_JwCkeOfU4,3626
|
|
26
25
|
mobile_mcp/utils/xml_formatter.py,sha256=uwTRb3vLbqhT8O-udzWT7s7LsV-DyDUz2DkofD3hXOE,4556
|
|
27
26
|
mobile_mcp/utils/xml_parser.py,sha256=QhL8CWbdmNDzmBLjtx6mEnjHgMFZzJeHpCL15qfXSpI,3926
|
|
28
|
-
mobile_mcp_ai-2.
|
|
29
|
-
mobile_mcp_ai-2.
|
|
30
|
-
mobile_mcp_ai-2.
|
|
31
|
-
mobile_mcp_ai-2.
|
|
32
|
-
mobile_mcp_ai-2.
|
|
33
|
-
mobile_mcp_ai-2.
|
|
27
|
+
mobile_mcp_ai-2.7.3.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
|
|
28
|
+
mobile_mcp_ai-2.7.3.dist-info/METADATA,sha256=60jvdRSHcL4QWPCaeiFxLunyBbT_2wkYF0UIo-Fq4v0,11505
|
|
29
|
+
mobile_mcp_ai-2.7.3.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
|
|
30
|
+
mobile_mcp_ai-2.7.3.dist-info/entry_points.txt,sha256=KB_FglozgPHBprSM1vFbIzGyheFuHFmGanscRdMJ_8A,68
|
|
31
|
+
mobile_mcp_ai-2.7.3.dist-info/top_level.txt,sha256=lLm6YpbTv855Lbh8BIA0rPxhybIrvYUzMEk9OErHT94,11
|
|
32
|
+
mobile_mcp_ai-2.7.3.dist-info/RECORD,,
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
"""
|
|
4
|
-
工具选择辅助函数 - 帮助 AI 选择正确的工具
|
|
5
|
-
|
|
6
|
-
这个模块提供工具选择建议,帮助 AI 在执行用例时遵循最佳实践。
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from typing import Dict, List, Optional, Tuple
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class ToolSelectionHelper:
|
|
13
|
-
"""工具选择辅助类"""
|
|
14
|
-
|
|
15
|
-
@staticmethod
|
|
16
|
-
def should_use_list_elements(scenario: str) -> Tuple[bool, str]:
|
|
17
|
-
"""判断是否应该使用 mobile_list_elements()
|
|
18
|
-
|
|
19
|
-
Args:
|
|
20
|
-
scenario: 使用场景描述
|
|
21
|
-
|
|
22
|
-
Returns:
|
|
23
|
-
(should_use, reason) 元组
|
|
24
|
-
"""
|
|
25
|
-
scenarios_should_use = [
|
|
26
|
-
"查找元素", "定位元素", "点击元素", "查找按钮", "查找文本",
|
|
27
|
-
"检查元素", "验证元素", "确认元素", "获取元素",
|
|
28
|
-
"进入页面", "新页面", "页面加载",
|
|
29
|
-
]
|
|
30
|
-
|
|
31
|
-
scenario_lower = scenario.lower()
|
|
32
|
-
for keyword in scenarios_should_use:
|
|
33
|
-
if keyword in scenario_lower:
|
|
34
|
-
return True, f"场景'{scenario}'应该优先使用 mobile_list_elements() 获取元素列表"
|
|
35
|
-
|
|
36
|
-
return False, ""
|
|
37
|
-
|
|
38
|
-
@staticmethod
|
|
39
|
-
def should_use_screenshot(scenario: str) -> Tuple[bool, str]:
|
|
40
|
-
"""判断是否应该使用截图
|
|
41
|
-
|
|
42
|
-
Args:
|
|
43
|
-
scenario: 使用场景描述
|
|
44
|
-
|
|
45
|
-
Returns:
|
|
46
|
-
(should_use, reason) 元组
|
|
47
|
-
"""
|
|
48
|
-
scenarios_should_use = [
|
|
49
|
-
"视觉确认", "查看页面", "页面截图", "展示页面",
|
|
50
|
-
"控件树找不到", "元素不存在", "游戏", "Unity",
|
|
51
|
-
]
|
|
52
|
-
|
|
53
|
-
scenario_lower = scenario.lower()
|
|
54
|
-
for keyword in scenarios_should_use:
|
|
55
|
-
if keyword in scenario_lower:
|
|
56
|
-
return True, f"场景'{scenario}'可以使用截图作为兜底方案"
|
|
57
|
-
|
|
58
|
-
return False, ""
|
|
59
|
-
|
|
60
|
-
@staticmethod
|
|
61
|
-
def get_tool_selection_advice(action: str, elements: Optional[List[Dict]] = None) -> str:
|
|
62
|
-
"""获取工具选择建议
|
|
63
|
-
|
|
64
|
-
Args:
|
|
65
|
-
action: 要执行的操作(如"点击设置按钮")
|
|
66
|
-
elements: 已有的元素列表(如果有)
|
|
67
|
-
|
|
68
|
-
Returns:
|
|
69
|
-
工具选择建议文本
|
|
70
|
-
"""
|
|
71
|
-
advice = []
|
|
72
|
-
|
|
73
|
-
# 如果还没有元素列表,建议先获取
|
|
74
|
-
if not elements:
|
|
75
|
-
advice.append("💡 建议:先调用 mobile_list_elements() 获取元素列表")
|
|
76
|
-
advice.append(" 然后使用 mobile_click_by_text() 或 mobile_click_by_id() 点击")
|
|
77
|
-
advice.append(" 仅在控件树找不到元素时,才使用 mobile_screenshot_with_som()")
|
|
78
|
-
else:
|
|
79
|
-
advice.append("✅ 已有元素列表,可以直接使用 mobile_click_by_text() 或 mobile_click_by_id()")
|
|
80
|
-
advice.append(" 无需再次调用 mobile_list_elements() 或截图")
|
|
81
|
-
|
|
82
|
-
return "\n".join(advice)
|
|
83
|
-
|
|
84
|
-
@staticmethod
|
|
85
|
-
def find_element_in_list(elements: List[Dict],
|
|
86
|
-
text: Optional[str] = None,
|
|
87
|
-
resource_id: Optional[str] = None,
|
|
88
|
-
content_desc: Optional[str] = None) -> Optional[Dict]:
|
|
89
|
-
"""在元素列表中查找元素
|
|
90
|
-
|
|
91
|
-
Args:
|
|
92
|
-
elements: 元素列表
|
|
93
|
-
text: 要查找的文本
|
|
94
|
-
resource_id: 要查找的 resource_id
|
|
95
|
-
content_desc: 要查找的 content_desc
|
|
96
|
-
|
|
97
|
-
Returns:
|
|
98
|
-
找到的元素,如果未找到返回 None
|
|
99
|
-
"""
|
|
100
|
-
for elem in elements:
|
|
101
|
-
if text and elem.get("text") == text:
|
|
102
|
-
return elem
|
|
103
|
-
if resource_id and elem.get("resource_id") == resource_id:
|
|
104
|
-
return elem
|
|
105
|
-
if content_desc and elem.get("content_desc") == content_desc:
|
|
106
|
-
return elem
|
|
107
|
-
|
|
108
|
-
return None
|
|
109
|
-
|
|
110
|
-
@staticmethod
|
|
111
|
-
def suggest_click_method(elements: List[Dict], target_text: str) -> Dict:
|
|
112
|
-
"""建议点击方法
|
|
113
|
-
|
|
114
|
-
Args:
|
|
115
|
-
elements: 元素列表
|
|
116
|
-
target_text: 目标文本
|
|
117
|
-
|
|
118
|
-
Returns:
|
|
119
|
-
建议信息字典
|
|
120
|
-
"""
|
|
121
|
-
# 查找元素
|
|
122
|
-
target_elem = ToolSelectionHelper.find_element_in_list(elements, text=target_text)
|
|
123
|
-
|
|
124
|
-
if target_elem:
|
|
125
|
-
# 元素存在,建议使用文本点击
|
|
126
|
-
return {
|
|
127
|
-
"found": True,
|
|
128
|
-
"method": "mobile_click_by_text",
|
|
129
|
-
"params": {"text": target_text},
|
|
130
|
-
"reason": f"元素'{target_text}'在控件树中找到,建议使用 mobile_click_by_text('{target_text}')"
|
|
131
|
-
}
|
|
132
|
-
else:
|
|
133
|
-
# 元素不存在,建议使用截图
|
|
134
|
-
return {
|
|
135
|
-
"found": False,
|
|
136
|
-
"method": "mobile_screenshot_with_som",
|
|
137
|
-
"params": {},
|
|
138
|
-
"reason": f"元素'{target_text}'在控件树中未找到,建议使用 mobile_screenshot_with_som() 作为兜底方案"
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
# 导出辅助函数
|
|
143
|
-
def should_use_list_elements_first(action: str) -> bool:
|
|
144
|
-
"""判断是否应该优先使用 list_elements
|
|
145
|
-
|
|
146
|
-
Args:
|
|
147
|
-
action: 要执行的操作描述
|
|
148
|
-
|
|
149
|
-
Returns:
|
|
150
|
-
是否应该优先使用 list_elements
|
|
151
|
-
"""
|
|
152
|
-
helper = ToolSelectionHelper()
|
|
153
|
-
should_use, _ = helper.should_use_list_elements(action)
|
|
154
|
-
return should_use
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def get_click_suggestion(elements: List[Dict], target_text: str) -> Dict:
|
|
158
|
-
"""获取点击建议
|
|
159
|
-
|
|
160
|
-
Args:
|
|
161
|
-
elements: 元素列表
|
|
162
|
-
target_text: 目标文本
|
|
163
|
-
|
|
164
|
-
Returns:
|
|
165
|
-
点击建议字典
|
|
166
|
-
"""
|
|
167
|
-
helper = ToolSelectionHelper()
|
|
168
|
-
return helper.suggest_click_method(elements, target_text)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|