mobile-mcp-ai 2.7.4__py3-none-any.whl → 2.7.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.
- mobile_mcp/core/basic_tools_lite.py +248 -36
- mobile_mcp/mcp_tools/mcp_server.py +20 -1
- {mobile_mcp_ai-2.7.4.dist-info → mobile_mcp_ai-2.7.6.dist-info}/METADATA +1 -1
- {mobile_mcp_ai-2.7.4.dist-info → mobile_mcp_ai-2.7.6.dist-info}/RECORD +8 -8
- {mobile_mcp_ai-2.7.4.dist-info → mobile_mcp_ai-2.7.6.dist-info}/WHEEL +0 -0
- {mobile_mcp_ai-2.7.4.dist-info → mobile_mcp_ai-2.7.6.dist-info}/entry_points.txt +0 -0
- {mobile_mcp_ai-2.7.4.dist-info → mobile_mcp_ai-2.7.6.dist-info}/licenses/LICENSE +0 -0
- {mobile_mcp_ai-2.7.4.dist-info → mobile_mcp_ai-2.7.6.dist-info}/top_level.txt +0 -0
|
@@ -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
|
-
|
|
3255
|
-
|
|
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(
|
|
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
|
-
|
|
3304
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
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 启动中... [
|
|
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,7 +1,7 @@
|
|
|
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=dflpTqtQ_3JyJ6SOAs2rJ3NZ998VDeDx4QWyfNB3NhI,232714
|
|
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
|
|
@@ -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=N5wKTUYrNWPruBILqrAjpvtso8Z3GRWCfMIR_aZxPLg,8649
|
|
21
21
|
mobile_mcp/mcp_tools/__init__.py,sha256=xkro8Rwqv_55YlVyhh-3DgRFSsLE3h1r31VIb3bpM6E,143
|
|
22
|
-
mobile_mcp/mcp_tools/mcp_server.py,sha256=
|
|
22
|
+
mobile_mcp/mcp_tools/mcp_server.py,sha256=RLqe4PqwVYVemLyShY6fKYUJ44pJg52k0VBJ6PTYyyQ,54402
|
|
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.7.
|
|
28
|
-
mobile_mcp_ai-2.7.
|
|
29
|
-
mobile_mcp_ai-2.7.
|
|
30
|
-
mobile_mcp_ai-2.7.
|
|
31
|
-
mobile_mcp_ai-2.7.
|
|
32
|
-
mobile_mcp_ai-2.7.
|
|
27
|
+
mobile_mcp_ai-2.7.6.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
|
|
28
|
+
mobile_mcp_ai-2.7.6.dist-info/METADATA,sha256=cnuQuEpsUmJ8JK-JSypMRsrb6wdZcKWZolHbrzWIp-o,11505
|
|
29
|
+
mobile_mcp_ai-2.7.6.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
30
|
+
mobile_mcp_ai-2.7.6.dist-info/entry_points.txt,sha256=KB_FglozgPHBprSM1vFbIzGyheFuHFmGanscRdMJ_8A,68
|
|
31
|
+
mobile_mcp_ai-2.7.6.dist-info/top_level.txt,sha256=lLm6YpbTv855Lbh8BIA0rPxhybIrvYUzMEk9OErHT94,11
|
|
32
|
+
mobile_mcp_ai-2.7.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|