mobile-mcp-ai 2.5.4__py3-none-any.whl → 2.5.6__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.
@@ -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), center_y, int(width * 0.2), center_y),
1609
- 'right': (int(width * 0.2), center_y, int(width * 0.8), center_y),
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
- self._record_operation('swipe', direction=direction)
1623
-
1624
- return {"success": True, "message": f"✅ 滑动成功: {direction}"}
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
 
@@ -2325,22 +2360,43 @@ class BasicMobileToolsLite:
2325
2360
  return 0.5
2326
2361
 
2327
2362
  def assert_text(self, text: str) -> Dict:
2328
- """检查页面是否包含文本"""
2363
+ """检查页面是否包含文本(支持精确匹配和包含匹配)"""
2329
2364
  try:
2365
+ exists = False
2366
+ match_type = ""
2367
+
2330
2368
  if self._is_ios():
2331
2369
  ios_client = self._get_ios_client()
2332
2370
  if ios_client and hasattr(ios_client, 'wda'):
2333
- exists = ios_client.wda(name=text).exists or ios_client.wda(label=text).exists
2334
- else:
2335
- exists = False
2371
+ # 先尝试精确匹配
2372
+ if ios_client.wda(name=text).exists or ios_client.wda(label=text).exists:
2373
+ exists = True
2374
+ match_type = "精确匹配"
2375
+ # 再尝试包含匹配
2376
+ elif ios_client.wda(nameContains=text).exists or ios_client.wda(labelContains=text).exists:
2377
+ exists = True
2378
+ match_type = "包含匹配"
2379
+ else:
2380
+ # Android: 先尝试精确匹配
2381
+ if self.client.u2(text=text).exists():
2382
+ exists = True
2383
+ match_type = "精确匹配"
2384
+ # 再尝试包含匹配
2385
+ elif self.client.u2(textContains=text).exists():
2386
+ exists = True
2387
+ match_type = "包含匹配"
2388
+
2389
+ if exists:
2390
+ message = f"✅ 文本'{text}' 存在({match_type})"
2336
2391
  else:
2337
- exists = self.client.u2(text=text).exists()
2392
+ message = f"❌ 文本'{text}' 不存在"
2338
2393
 
2339
2394
  return {
2340
2395
  "success": True,
2341
2396
  "found": exists,
2342
2397
  "text": text,
2343
- "message": f"✅ 文本'{text}' {'存在' if exists else '不存在'}"
2398
+ "match_type": match_type if exists else None,
2399
+ "message": message
2344
2400
  }
2345
2401
  except Exception as e:
2346
2402
  return {"success": False, "message": f"❌ 断言失败: {e}"}
@@ -34,9 +34,21 @@ from pathlib import Path
34
34
  from typing import Optional
35
35
 
36
36
  # 添加项目根目录到 Python 路径
37
- # __file__ 在 mcp/ 目录下,需要往上两级到项目根目录
38
- project_root = Path(__file__).parent.parent
39
- sys.path.insert(0, str(project_root))
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:
@@ -115,8 +127,19 @@ class MobileMCPServer:
115
127
  platform = self._detect_platform()
116
128
 
117
129
  try:
118
- from mobile_mcp.core.mobile_client import MobileClient
119
- from mobile_mcp.core.basic_tools_lite import BasicMobileToolsLite
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
120
143
 
121
144
  self.client = MobileClient(platform=platform)
122
145
  self.tools = BasicMobileToolsLite(self.client)
@@ -480,7 +503,11 @@ class MobileMCPServer:
480
503
  # ==================== 导航操作 ====================
481
504
  tools.append(Tool(
482
505
  name="mobile_swipe",
483
- 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
+ "- 两者都未指定时,使用屏幕中心高度",
484
511
  inputSchema={
485
512
  "type": "object",
486
513
  "properties": {
@@ -488,6 +515,14 @@ class MobileMCPServer:
488
515
  "type": "string",
489
516
  "enum": ["up", "down", "left", "right"],
490
517
  "description": "滑动方向"
518
+ },
519
+ "y": {
520
+ "type": "integer",
521
+ "description": "左右滑动时指定的高度坐标(像素,0-屏幕高度)"
522
+ },
523
+ "y_percent": {
524
+ "type": "number",
525
+ "description": "左右滑动时指定的高度百分比 (0-100)"
491
526
  }
492
527
  },
493
528
  "required": ["direction"]
@@ -880,7 +915,11 @@ class MobileMCPServer:
880
915
 
881
916
  # 导航
882
917
  elif name == "mobile_swipe":
883
- result = await self.tools.swipe(arguments["direction"])
918
+ result = await self.tools.swipe(
919
+ arguments["direction"],
920
+ y=arguments.get("y"),
921
+ y_percent=arguments.get("y_percent")
922
+ )
884
923
  return [TextContent(type="text", text=self.format_response(result))]
885
924
 
886
925
  elif name == "mobile_press_key":
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.1
2
2
  Name: mobile-mcp-ai
3
- Version: 2.5.4
3
+ Version: 2.5.6
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
@@ -31,21 +31,6 @@ Provides-Extra: ai
31
31
  Requires-Dist: dashscope>=1.10.0; extra == "ai"
32
32
  Requires-Dist: openai>=1.0.0; extra == "ai"
33
33
  Requires-Dist: anthropic>=0.3.0; extra == "ai"
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"
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"
43
- Provides-Extra: ios
44
- Requires-Dist: tidevice>=0.11.0; extra == "ios"
45
- Requires-Dist: facebook-wda>=1.4.0; extra == "ios"
46
- Provides-Extra: h5
47
- Requires-Dist: Appium-Python-Client>=3.0.0; extra == "h5"
48
- Requires-Dist: selenium>=4.0.0; extra == "h5"
49
34
  Provides-Extra: all
50
35
  Requires-Dist: dashscope>=1.10.0; extra == "all"
51
36
  Requires-Dist: openai>=1.0.0; extra == "all"
@@ -55,19 +40,21 @@ Requires-Dist: selenium>=4.0.0; extra == "all"
55
40
  Requires-Dist: pytest>=8.0.0; extra == "all"
56
41
  Requires-Dist: pytest-asyncio>=0.21.0; extra == "all"
57
42
  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
43
+ Provides-Extra: dev
44
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
45
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
46
+ Requires-Dist: twine>=4.0.0; extra == "dev"
47
+ Requires-Dist: build>=0.10.0; extra == "dev"
48
+ Provides-Extra: h5
49
+ Requires-Dist: Appium-Python-Client>=3.0.0; extra == "h5"
50
+ Requires-Dist: selenium>=4.0.0; extra == "h5"
51
+ Provides-Extra: ios
52
+ Requires-Dist: tidevice>=0.11.0; extra == "ios"
53
+ Requires-Dist: facebook-wda>=1.4.0; extra == "ios"
54
+ Provides-Extra: test
55
+ Requires-Dist: pytest>=8.0.0; extra == "test"
56
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "test"
57
+ Requires-Dist: allure-pytest>=2.13.0; extra == "test"
71
58
 
72
59
  # 📱 Mobile MCP AI
73
60
 
@@ -408,10 +395,18 @@ tidevice list
408
395
  |:---:|------|------|
409
396
  | 📋 | `mobile_list_elements` | 列出页面元素 |
410
397
  | 📸 | `mobile_take_screenshot` | 截图 |
398
+ | 📸 | `mobile_screenshot_with_som` | Set-of-Mark 截图(智能标注) |
399
+ | 📸 | `mobile_screenshot_with_grid` | 带网格坐标的截图 |
411
400
  | 📐 | `mobile_get_screen_size` | 屏幕尺寸 |
412
401
  | 👆 | `mobile_click_by_text` | 文本点击 |
413
402
  | 👆 | `mobile_click_by_id` | ID 点击 |
414
403
  | 👆 | `mobile_click_at_coords` | 坐标点击 |
404
+ | 👆 | `mobile_click_by_percent` | 百分比点击 |
405
+ | 👆 | `mobile_click_by_som` | SoM 编号点击 |
406
+ | 👆 | `mobile_long_press_by_id` | ID 长按 |
407
+ | 👆 | `mobile_long_press_by_text` | 文本长按 |
408
+ | 👆 | `mobile_long_press_by_percent` | 百分比长按 |
409
+ | 👆 | `mobile_long_press_at_coords` | 坐标长按 |
415
410
  | ⌨️ | `mobile_input_text_by_id` | ID 输入 |
416
411
  | ⌨️ | `mobile_input_at_coords` | 坐标输入 |
417
412
  | 👆 | `mobile_swipe` | 滑动 |
@@ -422,6 +417,11 @@ tidevice list
422
417
  | 📦 | `mobile_list_apps` | 列出应用 |
423
418
  | 📱 | `mobile_list_devices` | 列出设备 |
424
419
  | 🔌 | `mobile_check_connection` | 检查连接 |
420
+ | 🔍 | `mobile_find_close_button` | 查找关闭按钮 |
421
+ | 🚫 | `mobile_close_popup` | 关闭弹窗 |
422
+ | 🚫 | `mobile_close_ad` | 智能关闭广告弹窗 |
423
+ | 🎯 | `mobile_template_close` | 模板匹配关闭弹窗 |
424
+ | ➕ | `mobile_template_add` | 添加 X 号模板 |
425
425
  | ✅ | `mobile_assert_text` | 断言文本 |
426
426
  | 📜 | `mobile_get_operation_history` | 操作历史 |
427
427
  | 🗑️ | `mobile_clear_operation_history` | 清空历史 |
@@ -1,7 +1,7 @@
1
1
  mobile_mcp/__init__.py,sha256=sQJZTL_sxQFzmcS7jOtS2AHCfUySz40vhX96N6u1qy4,816
2
2
  mobile_mcp/config.py,sha256=yaFLAV4bc2wX0GQPtZDo7OYF9E88tXV-av41fQsJwK4,4480
3
3
  mobile_mcp/core/__init__.py,sha256=ndMy-cLAIsQDG5op7gM_AIplycqZSZPWEkec1pEhvEY,170
4
- mobile_mcp/core/basic_tools_lite.py,sha256=BzPT180GPQjhTNHaAKl46m1jvCM5KoQIPgySGdNSD30,149836
4
+ mobile_mcp/core/basic_tools_lite.py,sha256=BgzErXm0jfYaVK8Se8vCvMXqCudEnUYWI3yivyzxktg,152356
5
5
  mobile_mcp/core/device_manager.py,sha256=PX3-B5bJFnKNt6C8fT7FSY8JwD-ngZ3toF88bcOV9qA,8766
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
@@ -19,14 +19,14 @@ mobile_mcp/core/utils/logger.py,sha256=XXQAHUwT1jc70pq_tYFmL6f_nKrFlYm3hcgl-5RYR
19
19
  mobile_mcp/core/utils/operation_history_manager.py,sha256=gi8S8HJAMqvkUrY7_-kVbko3Xt7c4GAUziEujRd-N-Y,4792
20
20
  mobile_mcp/core/utils/smart_wait.py,sha256=PvKXImfN9Irru3bQJUjf4FLGn8LjY2VLzUNEl-i7xLE,8601
21
21
  mobile_mcp/mcp_tools/__init__.py,sha256=xkro8Rwqv_55YlVyhh-3DgRFSsLE3h1r31VIb3bpM6E,143
22
- mobile_mcp/mcp_tools/mcp_server.py,sha256=wqNOkjWYxxPYTAjU8e8yn8EyeRtO9NMhPTqGZ18jfnM,47471
22
+ mobile_mcp/mcp_tools/mcp_server.py,sha256=XP4nWbeNW0jM7QFvUiQ1DM0CU3UMW8Do8uxwRLAARYc,49527
23
23
  mobile_mcp/utils/__init__.py,sha256=8EH0i7UGtx1y_j_GEgdN-cZdWn2sRtZSEOLlNF9HRnY,158
24
24
  mobile_mcp/utils/logger.py,sha256=Sqq2Nr0Y4p03erqcrbYKVPCGiFaNGHMcE_JwCkeOfU4,3626
25
25
  mobile_mcp/utils/xml_formatter.py,sha256=uwTRb3vLbqhT8O-udzWT7s7LsV-DyDUz2DkofD3hXOE,4556
26
26
  mobile_mcp/utils/xml_parser.py,sha256=QhL8CWbdmNDzmBLjtx6mEnjHgMFZzJeHpCL15qfXSpI,3926
27
- mobile_mcp_ai-2.5.4.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
28
- mobile_mcp_ai-2.5.4.dist-info/METADATA,sha256=EUiIZ3uxIiyK-P9Sf558k8nu6dAWnl1U6mcjBby0Lso,9745
29
- mobile_mcp_ai-2.5.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
30
- mobile_mcp_ai-2.5.4.dist-info/entry_points.txt,sha256=KB_FglozgPHBprSM1vFbIzGyheFuHFmGanscRdMJ_8A,68
31
- mobile_mcp_ai-2.5.4.dist-info/top_level.txt,sha256=lLm6YpbTv855Lbh8BIA0rPxhybIrvYUzMEk9OErHT94,11
32
- mobile_mcp_ai-2.5.4.dist-info/RECORD,,
27
+ mobile_mcp_ai-2.5.6.dist-info/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
28
+ mobile_mcp_ai-2.5.6.dist-info/METADATA,sha256=dd3JeAJ-53sZNKzN9-ga11MFJ8rf4YF9Cb8RX6fYq64,10213
29
+ mobile_mcp_ai-2.5.6.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
30
+ mobile_mcp_ai-2.5.6.dist-info/entry_points.txt,sha256=KB_FglozgPHBprSM1vFbIzGyheFuHFmGanscRdMJ_8A,68
31
+ mobile_mcp_ai-2.5.6.dist-info/top_level.txt,sha256=lLm6YpbTv855Lbh8BIA0rPxhybIrvYUzMEk9OErHT94,11
32
+ mobile_mcp_ai-2.5.6.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: bdist_wheel (0.45.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5