mobile-mcp-ai 2.7.4__tar.gz → 2.7.6__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 (47) hide show
  1. {mobile_mcp_ai-2.7.4/mobile_mcp_ai.egg-info → mobile_mcp_ai-2.7.6}/PKG-INFO +1 -1
  2. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/core/basic_tools_lite.py +248 -36
  3. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/mcp_tools/mcp_server.py +20 -1
  4. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6/mobile_mcp_ai.egg-info}/PKG-INFO +1 -1
  5. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/setup.py +1 -1
  6. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/LICENSE +0 -0
  7. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/MANIFEST.in +0 -0
  8. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/README.md +0 -0
  9. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/__init__.py +0 -0
  10. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/config.py +0 -0
  11. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/core/__init__.py +0 -0
  12. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/core/device_manager.py +0 -0
  13. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/core/dynamic_config.py +0 -0
  14. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/core/ios_client_wda.py +0 -0
  15. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/core/ios_device_manager_wda.py +0 -0
  16. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/core/mobile_client.py +0 -0
  17. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/core/template_matcher.py +0 -0
  18. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/core/templates/close_buttons/auto_x_0112_151217.png +0 -0
  19. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/core/templates/close_buttons/auto_x_0112_152037.png +0 -0
  20. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/core/templates/close_buttons/auto_x_0112_152840.png +0 -0
  21. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/core/templates/close_buttons/auto_x_0112_153256.png +0 -0
  22. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/core/templates/close_buttons/auto_x_0112_154847.png +0 -0
  23. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/core/templates/close_buttons/gray_x_stock_ad.png +0 -0
  24. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/core/utils/__init__.py +0 -0
  25. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/core/utils/logger.py +0 -0
  26. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/core/utils/operation_history_manager.py +0 -0
  27. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/core/utils/smart_wait.py +0 -0
  28. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/docs/iOS_SETUP_GUIDE.md +0 -0
  29. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/mcp_tools/__init__.py +0 -0
  30. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/mobile_mcp_ai.egg-info/SOURCES.txt +0 -0
  31. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/mobile_mcp_ai.egg-info/dependency_links.txt +0 -0
  32. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/mobile_mcp_ai.egg-info/entry_points.txt +0 -0
  33. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/mobile_mcp_ai.egg-info/not-zip-safe +0 -0
  34. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/mobile_mcp_ai.egg-info/requires.txt +0 -0
  35. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/mobile_mcp_ai.egg-info/top_level.txt +0 -0
  36. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/requirements.txt +0 -0
  37. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/setup.cfg +0 -0
  38. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/templates/close_buttons/auto_x_0112_151217.png +0 -0
  39. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/templates/close_buttons/auto_x_0112_152037.png +0 -0
  40. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/templates/close_buttons/auto_x_0112_152840.png +0 -0
  41. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/templates/close_buttons/auto_x_0112_153256.png +0 -0
  42. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/templates/close_buttons/auto_x_0112_154847.png +0 -0
  43. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/templates/close_buttons/gray_x_stock_ad.png +0 -0
  44. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/utils/__init__.py +0 -0
  45. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/utils/logger.py +0 -0
  46. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/utils/xml_formatter.py +0 -0
  47. {mobile_mcp_ai-2.7.4 → mobile_mcp_ai-2.7.6}/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.7.4
3
+ Version: 2.7.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
@@ -2687,6 +2687,11 @@ class BasicMobileToolsLite:
2687
2687
  # class 精简:只保留关键类型
2688
2688
  if class_name in ('EditText', 'TextInput', 'Button', 'ImageButton', 'CheckBox', 'Switch'):
2689
2689
  item['type'] = class_name
2690
+ # 重要:对于 ImageView 等图片类控件,即使没有其他属性,只要有 bounds 就应该返回
2691
+ # 因为 ImageView 可能是关闭按钮、图标等,对测试很重要
2692
+ if not item and bounds and class_name in ('ImageView', 'Image', 'ImageButton'):
2693
+ item['bounds'] = bounds
2694
+ item['type'] = class_name
2690
2695
  return item
2691
2696
 
2692
2697
  result = []
@@ -3185,6 +3190,10 @@ class BasicMobileToolsLite:
3185
3190
  center_x = (x1 + x2) // 2
3186
3191
  center_y = (y1 + y2) // 2
3187
3192
 
3193
+ # 计算相对位置(统一在循环开始计算,避免重复计算)
3194
+ rel_x = center_x / screen_width
3195
+ rel_y = center_y / screen_height
3196
+
3188
3197
  # 收集所有可点击元素(用于兜底策略:当只有一个可点击元素时点击它)
3189
3198
  if clickable:
3190
3199
  all_clickable_elements.append({
@@ -3215,6 +3224,22 @@ class BasicMobileToolsLite:
3215
3224
  in_popup = (px1 - margin_side <= center_x <= px2 + margin_side and
3216
3225
  py1 - margin_top <= center_y <= py2 + margin_bottom)
3217
3226
 
3227
+ # 【新增】兼容第三方广告页面:右上角的 ImageView 即使不在弹窗范围内,也可能是在弹窗上方的关闭按钮
3228
+ # 判断条件:ImageView 位于屏幕右上角(rel_x > 0.85, rel_y < 0.15)且尺寸合适
3229
+ is_top_right_imageview = (
3230
+ 'Image' in class_name and
3231
+ not clickable and
3232
+ rel_x > 0.85 and
3233
+ rel_y < 0.15 and
3234
+ 15 <= width <= 120 and
3235
+ 15 <= height <= 120
3236
+ )
3237
+
3238
+ # 如果是右上角 ImageView,即使不在弹窗范围内,也认为是关闭按钮候选
3239
+ if is_top_right_imageview:
3240
+ in_popup = True
3241
+ is_floating_close = True # 标记为浮动关闭按钮
3242
+
3218
3243
  # 检查是否是浮动关闭按钮(在弹窗外侧:上方或下方)
3219
3244
  # 上方浮动关闭按钮(常见:右上角外侧)
3220
3245
  if center_y < py1 and center_y > py1 - margin_top:
@@ -3241,8 +3266,14 @@ class BasicMobileToolsLite:
3241
3266
  # 浮动关闭按钮(在弹窗上方外侧)给予高额加分
3242
3267
  if is_floating_close:
3243
3268
  popup_edge_bonus += 5.0 # 大幅加分
3269
+ # 右上角 ImageView 额外加分(第三方广告页面常见)
3270
+ if is_top_right_imageview:
3271
+ popup_edge_bonus += 2.0 # 额外加分
3244
3272
  elif not popup_detected:
3245
- # 没有检测到弹窗时,只处理有明确关闭特征的元素
3273
+ # 没有检测到弹窗时,处理有明确关闭特征的元素
3274
+ # 同时,也考虑底部中央的 clickable 小元素(可能是关闭按钮)
3275
+ # 注意:右上角的 ImageView 只在有弹窗的情况下才识别,避免误识别正常页面的右上角图标
3276
+
3246
3277
  # 检查是否有明确的关闭特征(文本、resource-id、content-desc)
3247
3278
  has_explicit_close_feature = (
3248
3279
  text in close_texts or
@@ -3251,18 +3282,24 @@ class BasicMobileToolsLite:
3251
3282
  'dismiss' in resource_id.lower() or
3252
3283
  'cancel' in resource_id.lower()
3253
3284
  )
3254
- if not has_explicit_close_feature:
3255
- continue # 没有明确关闭特征,跳过
3256
- # 有明确关闭特征时,允许处理
3285
+
3286
+ # 【新增】底部中央的 clickable 小元素也可能是关闭按钮(常见于全屏广告、激励视频等)
3287
+ is_bottom_center_clickable = (
3288
+ clickable and
3289
+ rel_y > 0.75 and # 底部区域(屏幕下方 25%)
3290
+ 0.35 < rel_x < 0.65 and # 中央区域(屏幕中间 30%)
3291
+ width >= 20 and width <= 150 and # 合理尺寸
3292
+ height >= 20 and height <= 150
3293
+ )
3294
+
3295
+ if not has_explicit_close_feature and not is_bottom_center_clickable:
3296
+ continue # 没有明确关闭特征,且不是底部中央的 clickable 小元素,跳过
3297
+ # 有明确关闭特征或底部中央 clickable 小元素时,允许处理
3257
3298
  in_popup = True
3258
3299
 
3259
3300
  if not in_popup:
3260
3301
  continue
3261
3302
 
3262
- # 相对位置(0-1)
3263
- rel_x = center_x / screen_width
3264
- rel_y = center_y / screen_height
3265
-
3266
3303
  score = 0
3267
3304
  match_type = ""
3268
3305
  position = self._get_position_name(rel_x, rel_y)
@@ -3280,7 +3317,7 @@ class BasicMobileToolsLite:
3280
3317
  # ===== 策略3:clickable 的小尺寸元素(优先于非 clickable)=====
3281
3318
  elif clickable:
3282
3319
  min_size = max(20, int(screen_width * 0.03))
3283
- max_size = max(120, int(screen_width * 0.15))
3320
+ max_size = max(150, int(screen_width * 0.15)) # 扩大最大尺寸,兼容更大的关闭按钮
3284
3321
  if min_size <= width <= max_size and min_size <= height <= max_size:
3285
3322
  # clickable 元素基础分更高
3286
3323
  base_score = 8.0
@@ -3288,6 +3325,10 @@ class BasicMobileToolsLite:
3288
3325
  if is_floating_close:
3289
3326
  base_score = 12.0
3290
3327
  match_type = "floating_close"
3328
+ # 【新增】底部中央的 clickable 小元素(可能是关闭按钮,常见于全屏广告)
3329
+ elif rel_y > 0.75 and 0.35 < rel_x < 0.65:
3330
+ base_score = 10.0 # 给予较高分数
3331
+ match_type = "bottom_center_close"
3291
3332
  elif 'Image' in class_name:
3292
3333
  score = base_score + 2.0
3293
3334
  match_type = "clickable_image"
@@ -3296,12 +3337,19 @@ class BasicMobileToolsLite:
3296
3337
  score = base_score + self._get_position_score(rel_x, rel_y) + popup_edge_bonus
3297
3338
 
3298
3339
  # ===== 策略4:ImageView/ImageButton 类型的小元素(非 clickable)=====
3340
+ # 【增强】兼容第三方广告页面:右上角的 ImageView 即使 clickable="false" 也识别为关闭按钮
3299
3341
  elif 'Image' in class_name:
3300
3342
  min_size = max(15, int(screen_width * 0.02))
3301
3343
  max_size = max(120, int(screen_width * 0.12))
3302
3344
  if min_size <= width <= max_size and min_size <= height <= max_size:
3303
- score = 5.0 + self._get_position_score(rel_x, rel_y) + popup_edge_bonus
3304
- match_type = "ImageView"
3345
+ base_score = 5.0
3346
+ # 右上角的 ImageView 给予更高分数(第三方广告页面常见)
3347
+ if rel_x > 0.85 and rel_y < 0.15:
3348
+ base_score = 8.0 # 提高分数,优先识别
3349
+ match_type = "ImageView_top_right"
3350
+ else:
3351
+ match_type = "ImageView"
3352
+ score = base_score + self._get_position_score(rel_x, rel_y) + popup_edge_bonus
3305
3353
 
3306
3354
  # XML 顺序加分(后出现的元素在上层,更可能是弹窗内的元素)
3307
3355
  if score > 0:
@@ -3328,7 +3376,7 @@ class BasicMobileToolsLite:
3328
3376
  pass
3329
3377
 
3330
3378
  if not close_candidates:
3331
- # 兜底策略:如果检测到弹窗但未找到关闭按钮,且页面元素很少(只有1个可点击元素),直接点击它
3379
+ # 兜底策略1:如果检测到弹窗但未找到关闭按钮,且页面元素很少(只有1个可点击元素),直接点击它
3332
3380
  if popup_detected and popup_bounds and len(all_clickable_elements) == 1:
3333
3381
  single_element = all_clickable_elements[0]
3334
3382
  self.client.u2.click(single_element['center_x'], single_element['center_y'])
@@ -3354,6 +3402,37 @@ class BasicMobileToolsLite:
3354
3402
  result["returned"] = return_result['success']
3355
3403
  return result
3356
3404
 
3405
+ # 兜底策略2:即使未检测到弹窗,如果页面只有一个可点击元素,也尝试点击它(可能是特殊类型的弹窗)
3406
+ # 这种情况通常出现在:下载浮层、特殊弹窗等,它们的 resource-id 可能不包含 dialog/popup 等关键词
3407
+ if len(all_clickable_elements) == 1:
3408
+ single_element = all_clickable_elements[0]
3409
+ # 检查元素是否占据较大屏幕区域(可能是弹窗)
3410
+ element_area_ratio = (single_element['width'] * single_element['height']) / (screen_width * screen_height)
3411
+ # 如果元素占据屏幕 20% 以上,认为是可能的弹窗
3412
+ if element_area_ratio > 0.2:
3413
+ self.client.u2.click(single_element['center_x'], single_element['center_y'])
3414
+ time.sleep(0.5)
3415
+
3416
+ # 检查应用是否跳转
3417
+ app_check = self._check_app_switched()
3418
+ return_result = None
3419
+ if app_check['switched']:
3420
+ return_result = self._return_to_target_app()
3421
+
3422
+ # 记录操作
3423
+ rel_x = single_element['center_x'] / screen_width
3424
+ rel_y = single_element['center_y'] / screen_height
3425
+ self._record_click('percent', f"{round(rel_x * 100, 1)}%,{round(rel_y * 100, 1)}%",
3426
+ round(rel_x * 100, 1), round(rel_y * 100, 1),
3427
+ element_desc="唯一可点击元素(特殊弹窗兜底)")
3428
+
3429
+ result = {"success": True, "clicked": True, "method": "single_clickable_special_popup_fallback"}
3430
+ if app_check['switched']:
3431
+ result["switched"] = True
3432
+ if return_result:
3433
+ result["returned"] = return_result['success']
3434
+ return result
3435
+
3357
3436
  # 如果没有找到关闭按钮,且不满足兜底条件,返回fallback
3358
3437
  if popup_detected and popup_bounds:
3359
3438
  return {"success": False, "fallback": "vision", "popup": True}
@@ -3522,8 +3601,69 @@ class BasicMobileToolsLite:
3522
3601
  has_mask_layer = True
3523
3602
  mask_idx = elem['idx']
3524
3603
 
3525
- # 跳过全屏元素
3526
- if area_ratio > 0.9:
3604
+ # 先检查是否有强弹窗特征(用于后续判断)
3605
+ has_strong_popup_feature = (
3606
+ any(kw in class_name for kw in dialog_class_keywords) or
3607
+ any(kw in resource_id.lower() for kw in dialog_id_keywords) or
3608
+ any(kw in resource_id.lower() for kw in ad_popup_keywords) # 广告弹窗关键词
3609
+ )
3610
+
3611
+ # 检查是否有子元素是关闭按钮(作为弹窗特征)
3612
+ has_close_button_child = False
3613
+ elem_bounds = elem['bounds']
3614
+ for other_elem in all_elements:
3615
+ if other_elem['idx'] == elem['idx']:
3616
+ continue
3617
+ if other_elem['is_close_button']:
3618
+ # 检查关闭按钮是否在这个元素范围内
3619
+ ox1, oy1, ox2, oy2 = other_elem['bounds']
3620
+ ex1, ey1, ex2, ey2 = elem_bounds
3621
+ if ex1 <= ox1 and ey1 <= oy1 and ex2 >= ox2 and ey2 >= oy2:
3622
+ has_close_button_child = True
3623
+ break
3624
+
3625
+ # 检查是否有右上角的 ImageView 关闭按钮(全屏广告页常见)
3626
+ has_top_right_close = False
3627
+ if area_ratio > 0.9: # 全屏元素才检查
3628
+ for other_elem in all_elements:
3629
+ if other_elem['idx'] == elem['idx']:
3630
+ continue
3631
+ # 检查是否是右上角的 ImageView
3632
+ ox1, oy1, ox2, oy2 = other_elem['bounds']
3633
+ o_center_x = other_elem['center_x']
3634
+ o_center_y = other_elem['center_y']
3635
+ o_width = other_elem['width']
3636
+ o_height = other_elem['height']
3637
+ o_class = other_elem['class']
3638
+
3639
+ rel_x = o_center_x / screen_width
3640
+ rel_y = o_center_y / screen_height
3641
+
3642
+ # 右上角的 ImageView(即使 clickable="false")
3643
+ if ('Image' in o_class and
3644
+ rel_x > 0.85 and rel_y < 0.15 and
3645
+ 15 <= o_width <= 120 and 15 <= o_height <= 120):
3646
+ # 检查是否在当前元素范围内或附近
3647
+ if (ex1 <= ox1 and ey1 <= oy1 and ex2 >= ox2 and ey2 >= oy2) or \
3648
+ (abs(ex2 - ox1) < 50 and abs(ey1 - oy2) < 50): # 在元素右上角附近
3649
+ has_top_right_close = True
3650
+ break
3651
+
3652
+ # 【特殊处理】全屏广告页:如果面积 > 90% 但有关闭按钮或广告特征,也识别为弹窗
3653
+ is_fullscreen_ad = (
3654
+ area_ratio > 0.9 and
3655
+ (
3656
+ # 有关闭按钮作为子元素
3657
+ has_close_button_child or
3658
+ # 有右上角的 ImageView 关闭按钮
3659
+ has_top_right_close or
3660
+ # 有广告相关的强特征
3661
+ any(kw in resource_id.lower() for kw in ad_popup_keywords)
3662
+ )
3663
+ )
3664
+
3665
+ # 如果不是全屏广告页,跳过全屏元素
3666
+ if area_ratio > 0.9 and not is_fullscreen_ad:
3527
3667
  continue
3528
3668
 
3529
3669
  # 跳过太小的元素
@@ -3546,27 +3686,6 @@ class BasicMobileToolsLite:
3546
3686
  if 'search' in resource_id.lower() or 'Search' in class_name:
3547
3687
  continue # 跳过顶部搜索栏
3548
3688
 
3549
- # 先检查是否有强弹窗特征(用于后续判断)
3550
- has_strong_popup_feature = (
3551
- any(kw in class_name for kw in dialog_class_keywords) or
3552
- any(kw in resource_id.lower() for kw in dialog_id_keywords) or
3553
- any(kw in resource_id.lower() for kw in ad_popup_keywords) # 广告弹窗关键词
3554
- )
3555
-
3556
- # 检查是否有子元素是关闭按钮(作为弹窗特征)
3557
- has_close_button_child = False
3558
- elem_bounds = elem['bounds']
3559
- for other_elem in all_elements:
3560
- if other_elem['idx'] == elem['idx']:
3561
- continue
3562
- if other_elem['is_close_button']:
3563
- # 检查关闭按钮是否在这个元素范围内
3564
- ox1, oy1, ox2, oy2 = other_elem['bounds']
3565
- ex1, ey1, ex2, ey2 = elem_bounds
3566
- if ex1 <= ox1 and ey1 <= oy1 and ex2 >= ox2 and ey2 >= oy2:
3567
- has_close_button_child = True
3568
- break
3569
-
3570
3689
  # 【非弹窗特征】如果元素包含明显的页面内容特征,则不是弹窗
3571
3690
  # 检查是否包含视频播放器、内容列表等页面元素
3572
3691
  page_content_keywords = ['video', 'player', 'recycler', 'list', 'scroll', 'viewpager', 'fragment']
@@ -3605,6 +3724,10 @@ class BasicMobileToolsLite:
3605
3724
  if has_close_button_child:
3606
3725
  confidence += 0.3
3607
3726
 
3727
+ # 【强特征】全屏广告页且有右上角关闭按钮 (+0.4)
3728
+ if is_fullscreen_ad and has_top_right_close:
3729
+ confidence += 0.4
3730
+
3608
3731
  # 【中等特征】居中显示 (+0.2)
3609
3732
  # 但如果没有强特征,降低权重
3610
3733
  center_x = elem['center_x']
@@ -3616,7 +3739,8 @@ class BasicMobileToolsLite:
3616
3739
  any(kw in class_name for kw in dialog_class_keywords) or
3617
3740
  any(kw in resource_id.lower() for kw in dialog_id_keywords) or
3618
3741
  any(kw in resource_id.lower() for kw in ad_popup_keywords) or
3619
- has_close_button_child
3742
+ has_close_button_child or
3743
+ (is_fullscreen_ad and has_top_right_close) # 全屏广告页且有右上角关闭按钮
3620
3744
  )
3621
3745
 
3622
3746
  if is_centered_x and is_centered_y:
@@ -4812,4 +4936,92 @@ class BasicMobileToolsLite:
4812
4936
  return {"success": False, "error": f"需要安装依赖: {e}"}
4813
4937
  except Exception as e:
4814
4938
  return {"success": False, "error": f"添加模板失败: {e}"}
4939
+
4940
+ def open_new_chat(self, message: str = "继续执行飞书用例") -> Dict:
4941
+ """打开 Cursor 新会话并发送消息
4942
+
4943
+ 用于飞书用例批量执行时,自动分批继续。
4944
+
4945
+ Args:
4946
+ message: 发送到新会话的消息,默认"继续执行飞书用例"
4947
+
4948
+ Returns:
4949
+ 执行结果
4950
+
4951
+ 依赖:
4952
+ pip install pyautogui pyperclip pygetwindow (macOS/Windows)
4953
+ """
4954
+ import sys
4955
+ import platform
4956
+
4957
+ try:
4958
+ import pyautogui
4959
+ import pyperclip
4960
+ except ImportError:
4961
+ return {
4962
+ "success": False,
4963
+ "error": "缺少依赖,请执行: pip install pyautogui pyperclip pygetwindow"
4964
+ }
4965
+
4966
+ try:
4967
+ system = platform.system()
4968
+
4969
+ # 1. 激活 Cursor 窗口
4970
+ if system == "Darwin": # macOS
4971
+ import subprocess
4972
+ # 使用 osascript 激活 Cursor
4973
+ script = '''
4974
+ tell application "Cursor"
4975
+ activate
4976
+ end tell
4977
+ '''
4978
+ subprocess.run(["osascript", "-e", script], check=True)
4979
+ time.sleep(0.3)
4980
+
4981
+ # 2. 快捷键打开新会话 (Cmd+T)
4982
+ pyautogui.hotkey('command', 't')
4983
+
4984
+ elif system == "Windows":
4985
+ try:
4986
+ import pygetwindow as gw
4987
+ cursor_windows = gw.getWindowsWithTitle('Cursor')
4988
+ if cursor_windows:
4989
+ cursor_windows[0].activate()
4990
+ time.sleep(0.3)
4991
+ except:
4992
+ pass # 如果激活失败,继续尝试发送快捷键
4993
+
4994
+ # 2. 快捷键打开新会话 (Ctrl+T)
4995
+ pyautogui.hotkey('ctrl', 't')
4996
+
4997
+ else: # Linux
4998
+ # 2. 快捷键打开新会话 (Ctrl+T)
4999
+ pyautogui.hotkey('ctrl', 't')
5000
+
5001
+ time.sleep(0.5) # 等待新会话打开
5002
+
5003
+ # 3. 复制消息到剪贴板并粘贴
5004
+ pyperclip.copy(message)
5005
+ time.sleep(0.1)
5006
+
5007
+ if system == "Darwin":
5008
+ pyautogui.hotkey('command', 'v')
5009
+ else:
5010
+ pyautogui.hotkey('ctrl', 'v')
5011
+
5012
+ time.sleep(0.2)
5013
+
5014
+ # 4. 按 Enter 发送
5015
+ pyautogui.press('enter')
5016
+
5017
+ return {
5018
+ "success": True,
5019
+ "message": f"✅ 已打开新会话并发送: {message}"
5020
+ }
5021
+
5022
+ except Exception as e:
5023
+ return {
5024
+ "success": False,
5025
+ "error": f"打开新会话失败: {e}"
5026
+ }
4815
5027
 
@@ -867,6 +867,19 @@ class MobileMCPServer:
867
867
  }
868
868
  ))
869
869
 
870
+ # ==================== Cursor 会话管理 ====================
871
+ tools.append(Tool(
872
+ name="mobile_open_new_chat",
873
+ description="🆕 打开Cursor新会话。用于飞书用例批量执行时自动分批继续。",
874
+ inputSchema={
875
+ "type": "object",
876
+ "properties": {
877
+ "message": {"type": "string", "description": "发送到新会话的消息", "default": "继续执行飞书用例"}
878
+ },
879
+ "required": []
880
+ }
881
+ ))
882
+
870
883
  return tools
871
884
 
872
885
  async def handle_tool_call(self, name: str, arguments: dict):
@@ -1152,6 +1165,12 @@ class MobileMCPServer:
1152
1165
  result = {"success": False, "error": "请提供 x_percent/y_percent 或 screenshot_path/x/y/width/height"}
1153
1166
  return [TextContent(type="text", text=self.format_response(result))]
1154
1167
 
1168
+ # Cursor 会话管理
1169
+ elif name == "mobile_open_new_chat":
1170
+ message = arguments.get("message", "继续执行飞书用例")
1171
+ result = self.tools.open_new_chat(message)
1172
+ return [TextContent(type="text", text=self.format_response(result))]
1173
+
1155
1174
  else:
1156
1175
  return [TextContent(type="text", text=f"❌ 未知工具: {name}")]
1157
1176
 
@@ -1174,7 +1193,7 @@ async def async_main():
1174
1193
  async def call_tool(name: str, arguments: dict):
1175
1194
  return await server.handle_tool_call(name, arguments)
1176
1195
 
1177
- print("🚀 Mobile MCP Server 启动中... [26 个工具]", file=sys.stderr)
1196
+ print("🚀 Mobile MCP Server 启动中... [27 个工具]", file=sys.stderr)
1178
1197
  print("📱 支持 Android / iOS", file=sys.stderr)
1179
1198
  print("👁️ 完全依赖 Cursor 视觉能力,无需 AI 密钥", file=sys.stderr)
1180
1199
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mobile-mcp-ai
3
- Version: 2.7.4
3
+ Version: 2.7.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
@@ -25,7 +25,7 @@ if requirements_file.exists():
25
25
 
26
26
  setup(
27
27
  name="mobile-mcp-ai",
28
- version="2.7.4", # 优化_find_element_in_tree支持精确匹配和包含匹配,优化点击失败时使用控件中心坐标
28
+ version="2.7.6", # 更新飞书用例执行规则:执行状态改为执行结果,支持PASS/FAIL
29
29
  author="douzi",
30
30
  author_email="1492994674@qq.com",
31
31
  description="移动端自动化 MCP Server - 支持 Android/iOS,AI 功能可选(基础工具不需要 AI)",
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes