mobile-mcp-ai 2.6.4__tar.gz → 2.6.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.
Files changed (57) hide show
  1. {mobile-mcp-ai-2.6.4/mobile_mcp_ai.egg-info → mobile_mcp_ai-2.6.5}/PKG-INFO +43 -3
  2. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/core/basic_tools_lite.py +113 -5
  3. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/mcp_tools/mcp_server.py +54 -59
  4. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5/mobile_mcp_ai.egg-info}/PKG-INFO +43 -3
  5. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/mobile_mcp_ai.egg-info/SOURCES.txt +31 -1
  6. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/setup.py +1 -1
  7. mobile_mcp_ai-2.6.5/tests/test_mind_cloud_my_space.py +80 -0
  8. mobile_mcp_ai-2.6.5/tests/test_mind_correct.py +73 -0
  9. mobile_mcp_ai-2.6.5/tests/test_mind_improved.py +83 -0
  10. mobile_mcp_ai-2.6.5/tests/test_mind_optimized.py +77 -0
  11. mobile_mcp_ai-2.6.5/tests/test_open_mind.py +37 -0
  12. mobile_mcp_ai-2.6.5/tests/test_priority_demo.py +81 -0
  13. mobile_mcp_ai-2.6.5/tests/test_simple.py +76 -0
  14. mobile_mcp_ai-2.6.5/tests/test_/344/270/276/346/212/245.py +136 -0
  15. mobile_mcp_ai-2.6.5/tests/test_/345/210/207/346/215/242/350/257/255/350/250/200/345/210/260English.py +158 -0
  16. mobile_mcp_ai-2.6.5/tests/test_/346/265/213/350/257/225.py +114 -0
  17. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/LICENSE +0 -0
  18. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/MANIFEST.in +0 -0
  19. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/README.md +0 -0
  20. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/__init__.py +0 -0
  21. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/config.py +0 -0
  22. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/core/__init__.py +0 -0
  23. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/core/device_manager.py +0 -0
  24. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/core/dynamic_config.py +0 -0
  25. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/core/ios_client_wda.py +0 -0
  26. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/core/ios_device_manager_wda.py +0 -0
  27. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/core/mobile_client.py +0 -0
  28. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/core/template_matcher.py +0 -0
  29. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/core/templates/close_buttons/auto_x_0112_151217.png +0 -0
  30. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/core/templates/close_buttons/auto_x_0112_152037.png +0 -0
  31. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/core/templates/close_buttons/auto_x_0112_152840.png +0 -0
  32. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/core/templates/close_buttons/auto_x_0112_153256.png +0 -0
  33. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/core/templates/close_buttons/auto_x_0112_154847.png +0 -0
  34. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/core/templates/close_buttons/gray_x_stock_ad.png +0 -0
  35. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/core/utils/__init__.py +0 -0
  36. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/core/utils/logger.py +0 -0
  37. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/core/utils/operation_history_manager.py +0 -0
  38. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/core/utils/smart_wait.py +0 -0
  39. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/docs/iOS_SETUP_GUIDE.md +0 -0
  40. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/mcp_tools/__init__.py +0 -0
  41. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/mobile_mcp_ai.egg-info/dependency_links.txt +0 -0
  42. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/mobile_mcp_ai.egg-info/entry_points.txt +0 -0
  43. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/mobile_mcp_ai.egg-info/not-zip-safe +0 -0
  44. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/mobile_mcp_ai.egg-info/requires.txt +0 -0
  45. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/mobile_mcp_ai.egg-info/top_level.txt +0 -0
  46. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/requirements.txt +0 -0
  47. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/setup.cfg +0 -0
  48. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/templates/close_buttons/auto_x_0112_151217.png +0 -0
  49. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/templates/close_buttons/auto_x_0112_152037.png +0 -0
  50. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/templates/close_buttons/auto_x_0112_152840.png +0 -0
  51. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/templates/close_buttons/auto_x_0112_153256.png +0 -0
  52. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/templates/close_buttons/auto_x_0112_154847.png +0 -0
  53. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/templates/close_buttons/gray_x_stock_ad.png +0 -0
  54. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/utils/__init__.py +0 -0
  55. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/utils/logger.py +0 -0
  56. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/utils/xml_formatter.py +0 -0
  57. {mobile-mcp-ai-2.6.4 → mobile_mcp_ai-2.6.5}/utils/xml_parser.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: mobile-mcp-ai
3
- Version: 2.6.4
3
+ Version: 2.6.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
@@ -21,13 +21,53 @@ Classifier: Topic :: Software Development :: Testing
21
21
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
22
  Requires-Python: >=3.8
23
23
  Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: uiautomator2>=2.16.0
26
+ Requires-Dist: adbutils>=1.2.0
27
+ Requires-Dist: Pillow>=10.0.0
28
+ Requires-Dist: mcp>=0.9.0
29
+ Requires-Dist: python-dotenv>=1.0.0
24
30
  Provides-Extra: ai
31
+ Requires-Dist: dashscope>=1.10.0; extra == "ai"
32
+ Requires-Dist: openai>=1.0.0; extra == "ai"
33
+ Requires-Dist: anthropic>=0.3.0; extra == "ai"
25
34
  Provides-Extra: test
35
+ Requires-Dist: pytest>=8.0.0; extra == "test"
36
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "test"
37
+ Requires-Dist: allure-pytest>=2.13.0; extra == "test"
26
38
  Provides-Extra: dev
39
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
40
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
41
+ Requires-Dist: twine>=4.0.0; extra == "dev"
42
+ Requires-Dist: build>=0.10.0; extra == "dev"
27
43
  Provides-Extra: ios
44
+ Requires-Dist: tidevice>=0.11.0; extra == "ios"
45
+ Requires-Dist: facebook-wda>=1.4.0; extra == "ios"
28
46
  Provides-Extra: h5
47
+ Requires-Dist: Appium-Python-Client>=3.0.0; extra == "h5"
48
+ Requires-Dist: selenium>=4.0.0; extra == "h5"
29
49
  Provides-Extra: all
30
- License-File: LICENSE
50
+ Requires-Dist: dashscope>=1.10.0; extra == "all"
51
+ Requires-Dist: openai>=1.0.0; extra == "all"
52
+ Requires-Dist: anthropic>=0.3.0; extra == "all"
53
+ Requires-Dist: Appium-Python-Client>=3.0.0; extra == "all"
54
+ Requires-Dist: selenium>=4.0.0; extra == "all"
55
+ Requires-Dist: pytest>=8.0.0; extra == "all"
56
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "all"
57
+ Requires-Dist: allure-pytest>=2.13.0; extra == "all"
58
+ Dynamic: author
59
+ Dynamic: author-email
60
+ Dynamic: classifier
61
+ Dynamic: description
62
+ Dynamic: description-content-type
63
+ Dynamic: home-page
64
+ Dynamic: keywords
65
+ Dynamic: license-file
66
+ Dynamic: project-url
67
+ Dynamic: provides-extra
68
+ Dynamic: requires-dist
69
+ Dynamic: requires-python
70
+ Dynamic: summary
31
71
 
32
72
  # 📱 Mobile MCP AI
33
73
 
@@ -1194,7 +1194,8 @@ class BasicMobileToolsLite:
1194
1194
  except Exception as e:
1195
1195
  return {"success": False, "message": f"❌ 百分比点击失败: {e}"}
1196
1196
 
1197
- def click_by_text(self, text: str, timeout: float = 3.0, position: Optional[str] = None) -> Dict:
1197
+ def click_by_text(self, text: str, timeout: float = 3.0, position: Optional[str] = None,
1198
+ verify: Optional[str] = None) -> Dict:
1198
1199
  """通过文本点击 - 先查 XML 树,再精准匹配
1199
1200
 
1200
1201
  Args:
@@ -1203,6 +1204,7 @@ class BasicMobileToolsLite:
1203
1204
  position: 位置信息,当有多个相同文案时使用。支持:
1204
1205
  - 垂直方向: "top"/"upper"/"上", "bottom"/"lower"/"下", "middle"/"center"/"中"
1205
1206
  - 水平方向: "left"/"左", "right"/"右", "center"/"中"
1207
+ verify: 可选,点击后验证的文本。如果指定,会检查该文本是否出现在页面上
1206
1208
  """
1207
1209
  try:
1208
1210
  if self._is_ios():
@@ -1215,7 +1217,12 @@ class BasicMobileToolsLite:
1215
1217
  elem.click()
1216
1218
  time.sleep(0.3)
1217
1219
  self._record_click('text', text, element_desc=text, locator_attr='text')
1218
- return {"success": True}
1220
+ # 验证逻辑
1221
+ if verify:
1222
+ return self._verify_after_click(verify, ios=True)
1223
+ # 返回页面文本摘要,方便确认页面变化
1224
+ page_texts = self._get_page_texts(10)
1225
+ return {"success": True, "page_texts": page_texts}
1219
1226
  # 控件树找不到,提示用视觉识别
1220
1227
  return {"success": False, "fallback": "vision", "msg": f"未找到'{text}',用截图点击"}
1221
1228
  else:
@@ -1248,7 +1255,12 @@ class BasicMobileToolsLite:
1248
1255
  time.sleep(0.3)
1249
1256
  self._record_click('text', attr_value, x_pct, y_pct,
1250
1257
  element_desc=f"{text}({position})", locator_attr=attr_type)
1251
- return {"success": True}
1258
+ # 验证逻辑
1259
+ if verify:
1260
+ return self._verify_after_click(verify)
1261
+ # 返回页面文本摘要
1262
+ page_texts = self._get_page_texts(10)
1263
+ return {"success": True, "page_texts": page_texts}
1252
1264
 
1253
1265
  # 没有位置参数时,使用选择器定位
1254
1266
  if attr_type == 'text':
@@ -1267,7 +1279,12 @@ class BasicMobileToolsLite:
1267
1279
  time.sleep(0.3)
1268
1280
  self._record_click('text', attr_value, x_pct, y_pct,
1269
1281
  element_desc=text, locator_attr=attr_type)
1270
- return {"success": True}
1282
+ # 验证逻辑
1283
+ if verify:
1284
+ return self._verify_after_click(verify)
1285
+ # 返回页面文本摘要
1286
+ page_texts = self._get_page_texts(10)
1287
+ return {"success": True, "page_texts": page_texts}
1271
1288
 
1272
1289
  # 选择器失败,用坐标兜底
1273
1290
  if bounds:
@@ -1277,13 +1294,58 @@ class BasicMobileToolsLite:
1277
1294
  time.sleep(0.3)
1278
1295
  self._record_click('percent', f"{x_pct}%,{y_pct}%", x_pct, y_pct,
1279
1296
  element_desc=text)
1280
- return {"success": True}
1297
+ # 验证逻辑
1298
+ if verify:
1299
+ return self._verify_after_click(verify)
1300
+ # 返回页面文本摘要
1301
+ page_texts = self._get_page_texts(10)
1302
+ return {"success": True, "page_texts": page_texts}
1281
1303
 
1282
1304
  # 控件树找不到,提示用视觉识别
1283
1305
  return {"success": False, "fallback": "vision", "msg": f"未找到'{text}',用截图点击"}
1284
1306
  except Exception as e:
1285
1307
  return {"success": False, "msg": str(e)}
1286
1308
 
1309
+ def _verify_after_click(self, verify_text: str, ios: bool = False, timeout: float = 2.0) -> Dict:
1310
+ """点击后验证期望文本是否出现
1311
+
1312
+ Args:
1313
+ verify_text: 期望出现的文本
1314
+ ios: 是否是 iOS 设备
1315
+ timeout: 验证超时时间
1316
+
1317
+ Returns:
1318
+ {"success": True, "verified": True/False, "hint": "..."}
1319
+ """
1320
+ time.sleep(0.5) # 等待页面更新
1321
+
1322
+ try:
1323
+ if ios:
1324
+ ios_client = self._get_ios_client()
1325
+ if ios_client and hasattr(ios_client, 'wda'):
1326
+ exists = ios_client.wda(name=verify_text).exists or \
1327
+ ios_client.wda(label=verify_text).exists
1328
+ else:
1329
+ exists = False
1330
+ else:
1331
+ # Android: 检查文本或包含文本
1332
+ exists = self.client.u2(text=verify_text).exists(timeout=timeout) or \
1333
+ self.client.u2(textContains=verify_text).exists(timeout=0.5) or \
1334
+ self.client.u2(description=verify_text).exists(timeout=0.5)
1335
+
1336
+ if exists:
1337
+ return {"success": True, "verified": True}
1338
+ else:
1339
+ # 验证失败,提示可以截图确认
1340
+ return {
1341
+ "success": True, # 点击本身成功
1342
+ "verified": False,
1343
+ "expect": verify_text,
1344
+ "hint": "验证失败,可截图确认"
1345
+ }
1346
+ except Exception as e:
1347
+ return {"success": True, "verified": False, "hint": f"验证异常: {e}"}
1348
+
1287
1349
  def _find_element_in_tree(self, text: str, position: Optional[str] = None) -> Optional[Dict]:
1288
1350
  """在 XML 树中查找包含指定文本的元素,优先返回可点击的元素
1289
1351
 
@@ -2387,6 +2449,52 @@ class BasicMobileToolsLite:
2387
2449
  except Exception as e:
2388
2450
  return [{"error": f"获取元素失败: {e}"}]
2389
2451
 
2452
+ def _get_page_texts(self, max_count: int = 15) -> List[str]:
2453
+ """获取页面关键文本列表(用于点击后快速确认页面变化)
2454
+
2455
+ Args:
2456
+ max_count: 最多返回的文本数量
2457
+
2458
+ Returns:
2459
+ 页面上的关键文本列表(去重)
2460
+ """
2461
+ try:
2462
+ if self._is_ios():
2463
+ ios_client = self._get_ios_client()
2464
+ if ios_client and hasattr(ios_client, 'wda'):
2465
+ # iOS: 获取所有 StaticText 的文本
2466
+ elements = ios_client.wda(type='XCUIElementTypeStaticText').find_elements()
2467
+ texts = set()
2468
+ for elem in elements[:50]: # 限制扫描数量
2469
+ try:
2470
+ name = elem.name or elem.label
2471
+ if name and len(name) > 1 and len(name) < 50:
2472
+ texts.add(name)
2473
+ except:
2474
+ pass
2475
+ return list(texts)[:max_count]
2476
+ return []
2477
+ else:
2478
+ # Android: 快速扫描 XML 获取文本
2479
+ xml_string = self.client.u2.dump_hierarchy(compressed=True)
2480
+ import xml.etree.ElementTree as ET
2481
+ root = ET.fromstring(xml_string)
2482
+
2483
+ texts = set()
2484
+ for elem in root.iter():
2485
+ text = elem.get('text', '').strip()
2486
+ desc = elem.get('content-desc', '').strip()
2487
+ # 只收集有意义的文本(长度2-30,非纯数字)
2488
+ for t in [text, desc]:
2489
+ if t and 2 <= len(t) <= 30 and not t.isdigit():
2490
+ texts.add(t)
2491
+ if len(texts) >= max_count * 2: # 收集足够后停止
2492
+ break
2493
+
2494
+ return list(texts)[:max_count]
2495
+ except Exception:
2496
+ return []
2497
+
2390
2498
  def _has_business_id(self, resource_id: str) -> bool:
2391
2499
  """
2392
2500
  判断resource_id是否是业务相关的ID
@@ -213,16 +213,16 @@ class MobileMCPServer:
213
213
 
214
214
  # ==================== 元素定位(优先使用)====================
215
215
  if compact:
216
- desc_list_elements = "📋 列出页面可交互元素。点击前先调用此工具获取 text/id 定位。"
216
+ desc_list_elements = "📋 【首选】列出页面元素(token低)。返回text/id用于点击,替代截图确认页面状态。"
217
217
  else:
218
- desc_list_elements = ("📋 列出页面所有可交互元素\n\n"
219
- "⚠️ 【重要】点击元素前必须先调用此工具!\n"
220
- "如果元素在控件树中存在,使用 click_by_text 或 click_by_id 定位。\n"
221
- "只有当此工具返回空或找不到目标元素时,才使用截图+坐标方式。\n\n"
222
- "📌 控件树定位优势:\n"
223
- "- 实时检测元素是否存在\n"
224
- "- 元素消失时会报错,不会误点击\n"
225
- "- 跨设备兼容性好")
218
+ desc_list_elements = ("📋 【⭐首选工具】列出页面所有可交互元素\n\n"
219
+ "🚀 Token 优化:返回文本数据(~500 tokens),远小于截图(~2000 tokens)!\n\n"
220
+ " 推荐使用场景:\n"
221
+ "- 点击前确认元素存在\n"
222
+ "- 点击后确认页面变化(替代截图确认)\n"
223
+ "- 获取 text/id 用于 click_by_text/click_by_id\n\n"
224
+ " 不要用截图确认页面,用此工具!\n"
225
+ "📌 只有需要看视觉布局时才用截图")
226
226
 
227
227
  tools.append(Tool(
228
228
  name="mobile_list_elements",
@@ -232,16 +232,15 @@ class MobileMCPServer:
232
232
 
233
233
  # ==================== 截图(视觉兜底)====================
234
234
  if compact:
235
- desc_screenshot = "📸 截图。推荐用 mobile_screenshot_with_som 代替(带元素编号)。"
235
+ desc_screenshot = "📸 截图(token高~2000)。优先用list_elements(~500)确认页面状态。"
236
236
  else:
237
- desc_screenshot = ("📸 截图查看屏幕内容\n\n"
238
- "⚠️ 【推荐使用 mobile_screenshot_with_som 代替!】\n"
239
- "SoM 截图会给元素标号,AI 可以直接说'点击几号',更精准!\n\n"
240
- "🎯 本工具仅用于:\n"
241
- "- 快速确认页面状态(不需要点击时)\n"
242
- "- 操作后确认结果\n"
243
- "- compress=false 时可获取原始分辨率截图(用于添加模板)\n\n"
244
- "💡 如需点击元素,请用 mobile_screenshot_with_som + mobile_click_by_som")
237
+ desc_screenshot = ("📸 截图查看屏幕内容(⚠️ Token 消耗高 ~2000)\n\n"
238
+ " 【不推荐】确认页面状态请用 list_elements(Token 仅 ~500)!\n\n"
239
+ " 仅在以下场景使用:\n"
240
+ "- 需要看视觉布局/图片内容\n"
241
+ "- 元素无 text/id,只能靠位置点击\n"
242
+ "- 调试问题需要可视化\n\n"
243
+ "💡 compress=false 可获取原图(用于添加模板)")
245
244
 
246
245
  tools.append(Tool(
247
246
  name="mobile_take_screenshot",
@@ -266,20 +265,18 @@ class MobileMCPServer:
266
265
  ))
267
266
 
268
267
  if compact:
269
- desc_som = "📸 SoM截图(推荐)。给元素标编号,用 click_by_som(编号) 点击。"
268
+ desc_som = "📸 SoM截图(token高)。元素有text时优先用list_elements+click_by_text。"
270
269
  else:
271
- desc_som = ("📸🏷️ Set-of-Mark 截图(⭐⭐ 强烈推荐!默认截图方式)\n\n"
272
- "【智能标注】给每个可点击元素画框+编号,检测弹窗时额外标注可能的X按钮位置(黄色)。\n"
273
- "AI 看图直接说'点击 3 号',调用 mobile_click_by_som(3) 即可!\n\n"
274
- "🎯 优势:\n"
275
- "- 元素有编号,精准点击不会误触\n"
276
- "- 自动检测弹窗,标注可能的关闭按钮位置\n"
277
- "- 适用于所有页面和所有操作\n\n"
278
- " 推荐流程:\n"
279
- "1. 任何需要操作的场景,都先调用此工具\n"
280
- "2. 看标注图,找到目标元素编号\n"
281
- "3. 调用 mobile_click_by_som(编号) 精准点击\n"
282
- "4. 🔴【必须】点击后再次截图确认操作是否成功!")
270
+ desc_som = ("📸🏷️ Set-of-Mark 截图(⚠️ Token 消耗高 ~2000)\n\n"
271
+ "【智能标注】给每个可点击元素画框+编号\n\n"
272
+ " 【不推荐常规使用】:\n"
273
+ "- 如果元素有 text,用 list_elements + click_by_text 更省 token\n"
274
+ "- 确认页面状态用 list_elements,不要截图确认!\n\n"
275
+ " 仅在以下场景使用:\n"
276
+ "- 元素无 text/id,只能看图点击\n"
277
+ "- 需要视觉布局信息\n"
278
+ "- 首次探索未知页面\n\n"
279
+ "💡 点击后用 list_elements 确认,不要再截图!")
283
280
 
284
281
  tools.append(Tool(
285
282
  name="mobile_screenshot_with_som",
@@ -314,18 +311,19 @@ class MobileMCPServer:
314
311
 
315
312
  # ==================== 点击操作 ====================
316
313
  if compact:
317
- desc_click_text = "👆 通过文本点击(最推荐)。position 可选 top/bottom/left/right。"
314
+ desc_click_text = "👆 文本点击(推荐)。verify可验证点击结果,无需截图确认。position可选top/bottom/left/right。"
318
315
  else:
319
- desc_click_text = ("👆 通过文本点击元素(最推荐)\n\n"
316
+ desc_click_text = ("👆 通过文本点击元素(⭐ 最推荐)\n\n"
320
317
  "✅ 最稳定的定位方式,跨设备兼容\n"
321
- "✅ 实时检测元素是否存在,元素不存在会报错\n"
322
- " 不会误点击到其他位置\n"
323
- "📋 使用前先调用 mobile_list_elements 确认元素文本\n"
324
- "💡 定位优先级:文本 > ID > 百分比 > 坐标\n\n"
325
- "📍 当页面有多个相同文案时,可使用 position 参数指定位置:\n"
326
- " - 垂直方向: \"top\"/\"upper\"/\"上\", \"bottom\"/\"lower\"/\"下\", \"middle\"/\"center\"/\"中\"\n"
327
- " - 水平方向: \"left\"/\"左\", \"right\"/\"右\", \"center\"/\"中\"\n"
328
- " 例如:点击\"底部\"的\"微剧\"tab,使用 position=\"bottom\"")
318
+ "✅ 元素不存在会报错,不会误点击\n\n"
319
+ "🚀 Token 优化流程:\n"
320
+ "1. list_elements 确认元素存在\n"
321
+ "2. click_by_text 点击\n"
322
+ "3. list_elements 确认页面变化(❌不要截图确认!)\n\n"
323
+ "📍 position 参数:多个相同文案时指定位置\n"
324
+ " - top/bottom/left/right/center\n\n"
325
+ "🔍 verify 参数:点击后自动验证文本是否出现\n"
326
+ " - 设置后无需额外调用 list_elements 确认")
329
327
 
330
328
  tools.append(Tool(
331
329
  name="mobile_click_by_text",
@@ -334,21 +332,21 @@ class MobileMCPServer:
334
332
  "type": "object",
335
333
  "properties": {
336
334
  "text": {"type": "string", "description": "元素文本"},
337
- "position": {"type": "string", "description": "位置:top/bottom/left/right"}
335
+ "position": {"type": "string", "description": "位置:top/bottom/left/right"},
336
+ "verify": {"type": "string", "description": "点击后验证的文本(可选)"}
338
337
  },
339
338
  "required": ["text"]
340
339
  }
341
340
  ))
342
341
 
343
342
  if compact:
344
- desc_click_id = "👆 通过 resource-id 点击。index 指定第几个(从 0 开始)。"
343
+ desc_click_id = "👆 通过resource-id点击。index指定第几个(从0开始)。点击后用list_elements确认。"
345
344
  else:
346
345
  desc_click_id = ("👆 通过 resource-id 点击元素(推荐)\n\n"
347
- "✅ 稳定的定位方式\n"
348
- " 实时检测元素是否存在,元素不存在会报错\n"
349
- "📋 使用前先调用 mobile_list_elements 获取元素 ID\n"
350
- "💡 当有多个相同 ID 的元素时,用 index 指定第几个(从 0 开始)\n"
351
- "💡 定位优先级:文本 > ID > 百分比 > 坐标")
346
+ "✅ 稳定的定位方式,元素不存在会报错\n"
347
+ "📋 使用前 list_elements 获取元素 ID\n"
348
+ "📋 点击后 list_elements 确认(❌不要截图确认!)\n"
349
+ "💡 多个相同 ID 时用 index 指定第几个(从 0 开始)")
352
350
 
353
351
  tools.append(Tool(
354
352
  name="mobile_click_by_id",
@@ -364,17 +362,13 @@ class MobileMCPServer:
364
362
  ))
365
363
 
366
364
  if compact:
367
- desc_click_coords = "👆 点击坐标(兜底)。优先用 click_by_text/id"
365
+ desc_click_coords = "👆 坐标点击(兜底)。优先用click_by_text/id,点击后用list_elements确认。"
368
366
  else:
369
- desc_click_coords = ("👆 点击指定坐标(兜底方案)\n\n"
370
- "⚠️ 【重要】优先使用 mobile_click_by_textmobile_click_by_id!\n"
371
- "仅在 mobile_list_elements 无法获取元素时使用此工具。\n\n"
372
- "⚠️ 【时序限制】截图分析期间页面可能变化:\n"
373
- "- 坐标是基于截图时刻的,点击时页面可能已不同\n"
374
- "- 如果误点击,调用 mobile_press_key(back) 返回\n"
375
- "- 对于定时弹窗(如广告),建议等待其自动消失\n\n"
376
- "📐 坐标转换:截图返回的 image_width/height 等参数直接传入即可\n\n"
377
- "🔴 【必须】点击后必须再次截图确认操作是否成功!")
367
+ desc_click_coords = ("👆 点击指定坐标(⚠️ 兜底方案)\n\n"
368
+ " 优先使用 click_by_textclick_by_id!\n"
369
+ "仅在 list_elements 无法获取元素时使用。\n\n"
370
+ "📐 坐标转换:截图返回的参数直接传入即可\n"
371
+ "📋 点击后用 list_elements 确认(❌不要截图确认!)")
378
372
 
379
373
  tools.append(Tool(
380
374
  name="mobile_click_at_coords",
@@ -863,7 +857,8 @@ class MobileMCPServer:
863
857
  elif name == "mobile_click_by_text":
864
858
  result = self.tools.click_by_text(
865
859
  arguments["text"],
866
- position=arguments.get("position")
860
+ position=arguments.get("position"),
861
+ verify=arguments.get("verify")
867
862
  )
868
863
  return [TextContent(type="text", text=self.format_response(result))]
869
864
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: mobile-mcp-ai
3
- Version: 2.6.4
3
+ Version: 2.6.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
@@ -21,13 +21,53 @@ Classifier: Topic :: Software Development :: Testing
21
21
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
22
  Requires-Python: >=3.8
23
23
  Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: uiautomator2>=2.16.0
26
+ Requires-Dist: adbutils>=1.2.0
27
+ Requires-Dist: Pillow>=10.0.0
28
+ Requires-Dist: mcp>=0.9.0
29
+ Requires-Dist: python-dotenv>=1.0.0
24
30
  Provides-Extra: ai
31
+ Requires-Dist: dashscope>=1.10.0; extra == "ai"
32
+ Requires-Dist: openai>=1.0.0; extra == "ai"
33
+ Requires-Dist: anthropic>=0.3.0; extra == "ai"
25
34
  Provides-Extra: test
35
+ Requires-Dist: pytest>=8.0.0; extra == "test"
36
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "test"
37
+ Requires-Dist: allure-pytest>=2.13.0; extra == "test"
26
38
  Provides-Extra: dev
39
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
40
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
41
+ Requires-Dist: twine>=4.0.0; extra == "dev"
42
+ Requires-Dist: build>=0.10.0; extra == "dev"
27
43
  Provides-Extra: ios
44
+ Requires-Dist: tidevice>=0.11.0; extra == "ios"
45
+ Requires-Dist: facebook-wda>=1.4.0; extra == "ios"
28
46
  Provides-Extra: h5
47
+ Requires-Dist: Appium-Python-Client>=3.0.0; extra == "h5"
48
+ Requires-Dist: selenium>=4.0.0; extra == "h5"
29
49
  Provides-Extra: all
30
- License-File: LICENSE
50
+ Requires-Dist: dashscope>=1.10.0; extra == "all"
51
+ Requires-Dist: openai>=1.0.0; extra == "all"
52
+ Requires-Dist: anthropic>=0.3.0; extra == "all"
53
+ Requires-Dist: Appium-Python-Client>=3.0.0; extra == "all"
54
+ Requires-Dist: selenium>=4.0.0; extra == "all"
55
+ Requires-Dist: pytest>=8.0.0; extra == "all"
56
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "all"
57
+ Requires-Dist: allure-pytest>=2.13.0; extra == "all"
58
+ Dynamic: author
59
+ Dynamic: author-email
60
+ Dynamic: classifier
61
+ Dynamic: description
62
+ Dynamic: description-content-type
63
+ Dynamic: home-page
64
+ Dynamic: keywords
65
+ Dynamic: license-file
66
+ Dynamic: project-url
67
+ Dynamic: provides-extra
68
+ Dynamic: requires-dist
69
+ Dynamic: requires-python
70
+ Dynamic: summary
31
71
 
32
72
  # 📱 Mobile MCP AI
33
73
 
@@ -1,6 +1,8 @@
1
1
  LICENSE
2
2
  MANIFEST.in
3
3
  README.md
4
+ __init__.py
5
+ config.py
4
6
  requirements.txt
5
7
  setup.py
6
8
  ./__init__.py
@@ -29,13 +31,27 @@ setup.py
29
31
  ./utils/logger.py
30
32
  ./utils/xml_formatter.py
31
33
  ./utils/xml_parser.py
34
+ core/__init__.py
35
+ core/basic_tools_lite.py
36
+ core/device_manager.py
37
+ core/dynamic_config.py
38
+ core/ios_client_wda.py
39
+ core/ios_device_manager_wda.py
40
+ core/mobile_client.py
41
+ core/template_matcher.py
32
42
  core/templates/close_buttons/auto_x_0112_151217.png
33
43
  core/templates/close_buttons/auto_x_0112_152037.png
34
44
  core/templates/close_buttons/auto_x_0112_152840.png
35
45
  core/templates/close_buttons/auto_x_0112_153256.png
36
46
  core/templates/close_buttons/auto_x_0112_154847.png
37
47
  core/templates/close_buttons/gray_x_stock_ad.png
48
+ core/utils/__init__.py
49
+ core/utils/logger.py
50
+ core/utils/operation_history_manager.py
51
+ core/utils/smart_wait.py
38
52
  docs/iOS_SETUP_GUIDE.md
53
+ mcp_tools/__init__.py
54
+ mcp_tools/mcp_server.py
39
55
  mobile_mcp_ai.egg-info/PKG-INFO
40
56
  mobile_mcp_ai.egg-info/SOURCES.txt
41
57
  mobile_mcp_ai.egg-info/dependency_links.txt
@@ -48,4 +64,18 @@ templates/close_buttons/auto_x_0112_152037.png
48
64
  templates/close_buttons/auto_x_0112_152840.png
49
65
  templates/close_buttons/auto_x_0112_153256.png
50
66
  templates/close_buttons/auto_x_0112_154847.png
51
- templates/close_buttons/gray_x_stock_ad.png
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
+ utils/__init__.py
79
+ utils/logger.py
80
+ utils/xml_formatter.py
81
+ utils/xml_parser.py
@@ -25,7 +25,7 @@ if requirements_file.exists():
25
25
 
26
26
  setup(
27
27
  name="mobile-mcp-ai",
28
- version="2.6.4", # Token 优化 - 减少约60% token消耗,启发式clickable判断
28
+ version="2.6.5", # 代码优化更新
29
29
  author="douzi",
30
30
  author_email="1492994674@qq.com",
31
31
  description="移动端自动化 MCP Server - 支持 Android/iOS,AI 功能可选(基础工具不需要 AI)",
@@ -0,0 +1,80 @@
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()