mobile-mcp-ai 2.3.3__tar.gz → 2.3.4__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.
- {mobile_mcp_ai-2.3.3/mobile_mcp_ai.egg-info → mobile_mcp_ai-2.3.4}/PKG-INFO +1 -1
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/core/basic_tools_lite.py +60 -102
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/core/mobile_client.py +22 -27
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/mcp_tools/mcp_server.py +19 -40
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4/mobile_mcp_ai.egg-info}/PKG-INFO +1 -1
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/setup.py +1 -1
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/LICENSE +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/MANIFEST.in +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/README.md +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/__init__.py +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/config.py +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/core/__init__.py +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/core/device_manager.py +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/core/dynamic_config.py +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/core/ios_client_wda.py +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/core/ios_device_manager_wda.py +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/core/utils/__init__.py +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/core/utils/logger.py +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/core/utils/operation_history_manager.py +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/core/utils/smart_wait.py +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/docs/iOS_SETUP_GUIDE.md +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/mcp_tools/__init__.py +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/mobile_mcp_ai.egg-info/SOURCES.txt +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/mobile_mcp_ai.egg-info/dependency_links.txt +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/mobile_mcp_ai.egg-info/entry_points.txt +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/mobile_mcp_ai.egg-info/not-zip-safe +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/mobile_mcp_ai.egg-info/requires.txt +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/mobile_mcp_ai.egg-info/top_level.txt +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/requirements.txt +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/setup.cfg +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/tests/test_mind_cloud_my_space.py +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/tests/test_mind_correct.py +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/tests/test_mind_improved.py +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/tests/test_mind_optimized.py +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/tests/test_open_mind.py +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/tests/test_priority_demo.py +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/tests/test_simple.py +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/tests/test_/344/270/276/346/212/245.py" +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/tests/test_/345/210/207/346/215/242/350/257/255/350/250/200/345/210/260English.py" +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/tests/test_/346/265/213/350/257/225.py" +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/utils/__init__.py +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/utils/logger.py +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/utils/xml_formatter.py +0 -0
- {mobile_mcp_ai-2.3.3 → mobile_mcp_ai-2.3.4}/utils/xml_parser.py +0 -0
|
@@ -109,6 +109,9 @@ class BasicMobileToolsLite:
|
|
|
109
109
|
img = Image.open(temp_path)
|
|
110
110
|
|
|
111
111
|
# 第3步:缩小尺寸(保持宽高比)
|
|
112
|
+
# 记录压缩后的图片尺寸(用于坐标转换)
|
|
113
|
+
image_width, image_height = img.width, img.height
|
|
114
|
+
|
|
112
115
|
if img.width > max_width:
|
|
113
116
|
ratio = max_width / img.width
|
|
114
117
|
new_w = max_width
|
|
@@ -125,6 +128,8 @@ class BasicMobileToolsLite:
|
|
|
125
128
|
# Pillow 旧版本
|
|
126
129
|
resample = Image.ANTIALIAS
|
|
127
130
|
img = img.resize((new_w, new_h), resample)
|
|
131
|
+
# 更新为压缩后的尺寸
|
|
132
|
+
image_width, image_height = new_w, new_h
|
|
128
133
|
|
|
129
134
|
# 第4步:生成最终文件名(JPEG 格式)
|
|
130
135
|
if description:
|
|
@@ -160,14 +165,18 @@ class BasicMobileToolsLite:
|
|
|
160
165
|
"screenshot_path": str(final_path),
|
|
161
166
|
"screen_width": screen_width,
|
|
162
167
|
"screen_height": screen_height,
|
|
168
|
+
"image_width": image_width,
|
|
169
|
+
"image_height": image_height,
|
|
163
170
|
"original_size": f"{original_size/1024:.1f}KB",
|
|
164
171
|
"compressed_size": f"{compressed_size/1024:.1f}KB",
|
|
165
172
|
"saved_percent": f"{saved_percent:.0f}%",
|
|
166
173
|
"message": f"📸 截图已保存: {final_path}\n"
|
|
167
174
|
f"📐 屏幕尺寸: {screen_width}x{screen_height}\n"
|
|
175
|
+
f"🖼️ 图片尺寸: {image_width}x{image_height}(AI 分析用)\n"
|
|
168
176
|
f"📦 已压缩: {original_size/1024:.0f}KB → {compressed_size/1024:.0f}KB (省 {saved_percent:.0f}%)\n"
|
|
169
|
-
f"
|
|
170
|
-
f"
|
|
177
|
+
f"⚠️ 【重要】AI 返回的坐标需要转换!\n"
|
|
178
|
+
f" 请使用 mobile_click_at_coords 并传入 image_width={image_width}, image_height={image_height}\n"
|
|
179
|
+
f" 工具会自动将图片坐标转换为屏幕坐标"
|
|
171
180
|
}
|
|
172
181
|
else:
|
|
173
182
|
# 不压缩,直接重命名临时文件
|
|
@@ -180,11 +189,14 @@ class BasicMobileToolsLite:
|
|
|
180
189
|
final_path = self.screenshot_dir / filename
|
|
181
190
|
temp_path.rename(final_path)
|
|
182
191
|
|
|
192
|
+
# 不压缩时,图片尺寸 = 屏幕尺寸
|
|
183
193
|
return {
|
|
184
194
|
"success": True,
|
|
185
195
|
"screenshot_path": str(final_path),
|
|
186
196
|
"screen_width": screen_width,
|
|
187
197
|
"screen_height": screen_height,
|
|
198
|
+
"image_width": screen_width,
|
|
199
|
+
"image_height": screen_height,
|
|
188
200
|
"file_size": f"{original_size/1024:.1f}KB",
|
|
189
201
|
"message": f"📸 截图已保存: {final_path}\n"
|
|
190
202
|
f"📐 屏幕尺寸: {screen_width}x{screen_height}\n"
|
|
@@ -226,11 +238,14 @@ class BasicMobileToolsLite:
|
|
|
226
238
|
width = info.get('displayWidth', 0)
|
|
227
239
|
height = info.get('displayHeight', 0)
|
|
228
240
|
|
|
241
|
+
# 不压缩时,图片尺寸 = 屏幕尺寸
|
|
229
242
|
return {
|
|
230
243
|
"success": True,
|
|
231
244
|
"screenshot_path": str(screenshot_path),
|
|
232
245
|
"screen_width": width,
|
|
233
246
|
"screen_height": height,
|
|
247
|
+
"image_width": width,
|
|
248
|
+
"image_height": height,
|
|
234
249
|
"message": f"📸 截图已保存: {screenshot_path}\n"
|
|
235
250
|
f"📐 屏幕尺寸: {width}x{height}\n"
|
|
236
251
|
f"⚠️ 未压缩(PIL 未安装),建议安装: pip install Pillow"
|
|
@@ -266,25 +281,51 @@ class BasicMobileToolsLite:
|
|
|
266
281
|
|
|
267
282
|
# ==================== 点击操作 ====================
|
|
268
283
|
|
|
269
|
-
def click_at_coords(self, x: int, y: int) -> Dict:
|
|
270
|
-
"""
|
|
284
|
+
def click_at_coords(self, x: int, y: int, image_width: int = 0, image_height: int = 0) -> Dict:
|
|
285
|
+
"""点击坐标(核心功能,支持自动坐标转换)
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
x: X 坐标(来自截图分析或屏幕坐标)
|
|
289
|
+
y: Y 坐标(来自截图分析或屏幕坐标)
|
|
290
|
+
image_width: 截图的宽度(可选,传入后自动转换坐标)
|
|
291
|
+
image_height: 截图的高度(可选,传入后自动转换坐标)
|
|
292
|
+
|
|
293
|
+
坐标转换说明:
|
|
294
|
+
如果截图被压缩过(如 1080→720),AI 返回的坐标是基于压缩图的。
|
|
295
|
+
传入 image_width/image_height 后,工具会自动将坐标转换为屏幕坐标。
|
|
296
|
+
"""
|
|
271
297
|
try:
|
|
272
|
-
#
|
|
298
|
+
# 获取屏幕尺寸
|
|
273
299
|
screen_width, screen_height = 0, 0
|
|
274
300
|
if self._is_ios():
|
|
275
301
|
ios_client = self._get_ios_client()
|
|
276
302
|
if ios_client and hasattr(ios_client, 'wda'):
|
|
277
|
-
ios_client.wda.click(x, y)
|
|
278
303
|
size = ios_client.wda.window_size()
|
|
279
304
|
screen_width, screen_height = size[0], size[1]
|
|
280
305
|
else:
|
|
281
306
|
return {"success": False, "message": "❌ iOS 客户端未初始化"}
|
|
282
307
|
else:
|
|
283
|
-
self.client.u2.click(x, y)
|
|
284
308
|
info = self.client.u2.info
|
|
285
309
|
screen_width = info.get('displayWidth', 0)
|
|
286
310
|
screen_height = info.get('displayHeight', 0)
|
|
287
311
|
|
|
312
|
+
# 🎯 坐标转换:如果传入了图片尺寸,将图片坐标转换为屏幕坐标
|
|
313
|
+
original_x, original_y = x, y
|
|
314
|
+
converted = False
|
|
315
|
+
if image_width > 0 and image_height > 0 and screen_width > 0 and screen_height > 0:
|
|
316
|
+
if image_width != screen_width or image_height != screen_height:
|
|
317
|
+
# 按比例转换坐标
|
|
318
|
+
x = int(x * screen_width / image_width)
|
|
319
|
+
y = int(y * screen_height / image_height)
|
|
320
|
+
converted = True
|
|
321
|
+
|
|
322
|
+
# 执行点击
|
|
323
|
+
if self._is_ios():
|
|
324
|
+
ios_client = self._get_ios_client()
|
|
325
|
+
ios_client.wda.click(x, y)
|
|
326
|
+
else:
|
|
327
|
+
self.client.u2.click(x, y)
|
|
328
|
+
|
|
288
329
|
time.sleep(0.3)
|
|
289
330
|
|
|
290
331
|
# 计算百分比坐标(用于跨设备兼容)
|
|
@@ -303,10 +344,18 @@ class BasicMobileToolsLite:
|
|
|
303
344
|
ref=f"coords_{x}_{y}"
|
|
304
345
|
)
|
|
305
346
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
347
|
+
if converted:
|
|
348
|
+
return {
|
|
349
|
+
"success": True,
|
|
350
|
+
"message": f"✅ 点击成功: ({x}, {y})\n"
|
|
351
|
+
f" 📐 坐标已转换: ({original_x},{original_y}) → ({x},{y})\n"
|
|
352
|
+
f" 🖼️ 图片尺寸: {image_width}x{image_height} → 屏幕: {screen_width}x{screen_height}"
|
|
353
|
+
}
|
|
354
|
+
else:
|
|
355
|
+
return {
|
|
356
|
+
"success": True,
|
|
357
|
+
"message": f"✅ 点击成功: ({x}, {y}) [相对位置: {x_percent}%, {y_percent}%]"
|
|
358
|
+
}
|
|
310
359
|
except Exception as e:
|
|
311
360
|
return {"success": False, "message": f"❌ 点击失败: {e}"}
|
|
312
361
|
|
|
@@ -813,97 +862,6 @@ class BasicMobileToolsLite:
|
|
|
813
862
|
except Exception as e:
|
|
814
863
|
return {"success": False, "message": f"❌ 断言失败: {e}"}
|
|
815
864
|
|
|
816
|
-
def close_ad(self, keywords: Optional[List[str]] = None, max_attempts: int = 3) -> Dict:
|
|
817
|
-
"""关闭广告弹窗
|
|
818
|
-
|
|
819
|
-
自动检测并点击广告关闭按钮,支持多种关闭方式:
|
|
820
|
-
1. 文本匹配:关闭、跳过、Skip、Close 等
|
|
821
|
-
2. 特殊符号:×、X、✕ 等
|
|
822
|
-
3. content-desc 匹配
|
|
823
|
-
|
|
824
|
-
Args:
|
|
825
|
-
keywords: 自定义关键词列表,默认使用内置关键词
|
|
826
|
-
max_attempts: 最大尝试次数,默认3次(处理多层弹窗)
|
|
827
|
-
|
|
828
|
-
Returns:
|
|
829
|
-
关闭结果,包含关闭的广告数量
|
|
830
|
-
"""
|
|
831
|
-
# 默认关键词(按优先级排序)
|
|
832
|
-
default_keywords = [
|
|
833
|
-
'关闭', '跳过', 'Skip', 'Close', 'close',
|
|
834
|
-
'×', 'X', '✕', '╳',
|
|
835
|
-
'我知道了', '稍后再说', '不再提示', '取消',
|
|
836
|
-
'知道了', '好的', '确定',
|
|
837
|
-
'Later', 'No thanks', 'Not now', 'Dismiss'
|
|
838
|
-
]
|
|
839
|
-
|
|
840
|
-
search_keywords = keywords if keywords else default_keywords
|
|
841
|
-
closed_count = 0
|
|
842
|
-
closed_items = []
|
|
843
|
-
|
|
844
|
-
try:
|
|
845
|
-
for attempt in range(max_attempts):
|
|
846
|
-
found_in_this_round = False
|
|
847
|
-
|
|
848
|
-
for keyword in search_keywords:
|
|
849
|
-
try:
|
|
850
|
-
if self._is_ios():
|
|
851
|
-
ios_client = self._get_ios_client()
|
|
852
|
-
if ios_client and hasattr(ios_client, 'wda'):
|
|
853
|
-
# iOS: 尝试 name 和 label
|
|
854
|
-
elem = ios_client.wda(name=keyword)
|
|
855
|
-
if not elem.exists:
|
|
856
|
-
elem = ios_client.wda(label=keyword)
|
|
857
|
-
if not elem.exists:
|
|
858
|
-
elem = ios_client.wda(nameContains=keyword)
|
|
859
|
-
|
|
860
|
-
if elem.exists:
|
|
861
|
-
elem.click()
|
|
862
|
-
time.sleep(0.5)
|
|
863
|
-
closed_count += 1
|
|
864
|
-
closed_items.append(keyword)
|
|
865
|
-
found_in_this_round = True
|
|
866
|
-
break
|
|
867
|
-
else:
|
|
868
|
-
# Android: 尝试 text 和 content-desc
|
|
869
|
-
elem = self.client.u2(text=keyword)
|
|
870
|
-
if not elem.exists(timeout=0.2):
|
|
871
|
-
elem = self.client.u2(textContains=keyword)
|
|
872
|
-
if not elem.exists(timeout=0.2):
|
|
873
|
-
elem = self.client.u2(description=keyword)
|
|
874
|
-
if not elem.exists(timeout=0.2):
|
|
875
|
-
elem = self.client.u2(descriptionContains=keyword)
|
|
876
|
-
|
|
877
|
-
if elem.exists(timeout=0.2):
|
|
878
|
-
elem.click()
|
|
879
|
-
time.sleep(0.5)
|
|
880
|
-
closed_count += 1
|
|
881
|
-
closed_items.append(keyword)
|
|
882
|
-
found_in_this_round = True
|
|
883
|
-
break
|
|
884
|
-
except Exception:
|
|
885
|
-
continue
|
|
886
|
-
|
|
887
|
-
if not found_in_this_round:
|
|
888
|
-
# 这一轮没找到广告,退出
|
|
889
|
-
break
|
|
890
|
-
|
|
891
|
-
if closed_count > 0:
|
|
892
|
-
return {
|
|
893
|
-
"success": True,
|
|
894
|
-
"closed_count": closed_count,
|
|
895
|
-
"closed_items": closed_items,
|
|
896
|
-
"message": f"✅ 已关闭 {closed_count} 个广告弹窗: {', '.join(closed_items)}"
|
|
897
|
-
}
|
|
898
|
-
else:
|
|
899
|
-
return {
|
|
900
|
-
"success": True,
|
|
901
|
-
"closed_count": 0,
|
|
902
|
-
"message": "✅ 未发现广告弹窗(或已全部关闭)"
|
|
903
|
-
}
|
|
904
|
-
except Exception as e:
|
|
905
|
-
return {"success": False, "message": f"❌ 关闭广告失败: {e}"}
|
|
906
|
-
|
|
907
865
|
# ==================== 脚本生成 ====================
|
|
908
866
|
|
|
909
867
|
def get_operation_history(self, limit: Optional[int] = None) -> Dict:
|
|
@@ -825,34 +825,29 @@ class MobileClient:
|
|
|
825
825
|
return {"success": False, "reason": str(e)}
|
|
826
826
|
|
|
827
827
|
# Android平台
|
|
828
|
-
# 🎯
|
|
828
|
+
# 🎯 优先使用智能启动(推荐)
|
|
829
829
|
if smart_wait:
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
return result
|
|
852
|
-
except ImportError:
|
|
853
|
-
# SmartAppLauncher 模块不存在,使用传统方式
|
|
854
|
-
print(f" 💡 智能启动模块未安装,使用传统启动方式", file=sys.stderr)
|
|
855
|
-
# 继续执行下面的传统方式
|
|
830
|
+
from .smart_app_launcher import SmartAppLauncher
|
|
831
|
+
launcher = SmartAppLauncher(self)
|
|
832
|
+
# 优化:快速模式,最多3秒
|
|
833
|
+
smart_wait_time = min(wait_time, 3)
|
|
834
|
+
|
|
835
|
+
# 🎯 从环境变量读取是否自动关闭广告(默认True)
|
|
836
|
+
import os
|
|
837
|
+
auto_close_ads = os.environ.get('AUTO_CLOSE_ADS', 'true').lower() in ['true', '1', 'yes']
|
|
838
|
+
|
|
839
|
+
result = await launcher.launch_with_smart_wait(
|
|
840
|
+
package_name,
|
|
841
|
+
max_wait=smart_wait_time,
|
|
842
|
+
auto_close_ads=auto_close_ads
|
|
843
|
+
)
|
|
844
|
+
|
|
845
|
+
# 打印截图路径(供Cursor AI查看验证)
|
|
846
|
+
if result.get('screenshot_path'):
|
|
847
|
+
print(f"\n📸 启动截图已保存: {result['screenshot_path']}", file=sys.stderr)
|
|
848
|
+
print(f"💡 提示: 请查看截图确认App是否已正确进入主页", file=sys.stderr)
|
|
849
|
+
|
|
850
|
+
return result
|
|
856
851
|
|
|
857
852
|
# 传统方式(快速启动,不等待加载)
|
|
858
853
|
print(f" 📱 启动App: {package_name}", file=sys.stderr)
|
|
@@ -156,12 +156,15 @@ class MobileMCPServer:
|
|
|
156
156
|
# ==================== 截图(视觉兜底)====================
|
|
157
157
|
tools.append(Tool(
|
|
158
158
|
name="mobile_take_screenshot",
|
|
159
|
-
description="📸
|
|
159
|
+
description="📸 截图(视觉定位用)。返回截图路径、屏幕尺寸和图片尺寸。\n\n"
|
|
160
160
|
"🎯 使用场景:\n"
|
|
161
161
|
"- 游戏(Unity/Cocos)无法获取元素时\n"
|
|
162
162
|
"- mobile_list_elements 返回空时\n"
|
|
163
163
|
"- 需要确认页面状态时\n\n"
|
|
164
|
-
"⚠️
|
|
164
|
+
"⚠️ 【重要】截图会被压缩!\n"
|
|
165
|
+
" - screen_width/screen_height = 原始屏幕尺寸\n"
|
|
166
|
+
" - image_width/image_height = 压缩后图片尺寸(AI 看到的)\n"
|
|
167
|
+
" - 点击时必须传入 image_width/image_height 让工具自动转换坐标!",
|
|
165
168
|
inputSchema={
|
|
166
169
|
"type": "object",
|
|
167
170
|
"properties": {
|
|
@@ -215,13 +218,17 @@ class MobileMCPServer:
|
|
|
215
218
|
"- 游戏(Unity/Cocos)无法获取元素\n"
|
|
216
219
|
"- mobile_list_elements 返回空\n"
|
|
217
220
|
"- 元素没有 id 和 text\n\n"
|
|
218
|
-
"
|
|
219
|
-
"
|
|
221
|
+
"⚠️ 【重要】如果坐标来自压缩截图,必须传入 image_width 和 image_height!\n"
|
|
222
|
+
" 截图返回的 image_width/image_height 字段就是需要传入的值。\n"
|
|
223
|
+
" 工具会自动将图片坐标转换为屏幕坐标。\n\n"
|
|
224
|
+
"✅ 自动记录百分比坐标,生成脚本时会转换为跨分辨率兼容的百分比定位",
|
|
220
225
|
inputSchema={
|
|
221
226
|
"type": "object",
|
|
222
227
|
"properties": {
|
|
223
|
-
"x": {"type": "number", "description": "X
|
|
224
|
-
"y": {"type": "number", "description": "Y
|
|
228
|
+
"x": {"type": "number", "description": "X 坐标(像素,来自截图分析或屏幕坐标)"},
|
|
229
|
+
"y": {"type": "number", "description": "Y 坐标(像素,来自截图分析或屏幕坐标)"},
|
|
230
|
+
"image_width": {"type": "number", "description": "截图的宽度(可选,传入后自动转换坐标)"},
|
|
231
|
+
"image_height": {"type": "number", "description": "截图的高度(可选,传入后自动转换坐标)"}
|
|
225
232
|
},
|
|
226
233
|
"required": ["x", "y"]
|
|
227
234
|
}
|
|
@@ -379,32 +386,6 @@ class MobileMCPServer:
|
|
|
379
386
|
}
|
|
380
387
|
))
|
|
381
388
|
|
|
382
|
-
tools.append(Tool(
|
|
383
|
-
name="mobile_close_ad",
|
|
384
|
-
description="📢 关闭广告弹窗(自动检测并点击关闭按钮)\n\n"
|
|
385
|
-
"🎯 自动检测以下关闭方式:\n"
|
|
386
|
-
"- 文本:关闭、跳过、Skip、Close、我知道了、稍后再说\n"
|
|
387
|
-
"- 符号:×、X、✕ 等\n"
|
|
388
|
-
"- 无障碍描述(content-desc)\n\n"
|
|
389
|
-
"💡 支持多层弹窗,最多尝试3次\n"
|
|
390
|
-
"✅ 比视觉识别更准确,推荐使用!",
|
|
391
|
-
inputSchema={
|
|
392
|
-
"type": "object",
|
|
393
|
-
"properties": {
|
|
394
|
-
"keywords": {
|
|
395
|
-
"type": "array",
|
|
396
|
-
"items": {"type": "string"},
|
|
397
|
-
"description": "自定义关键词列表(可选,默认使用内置关键词)"
|
|
398
|
-
},
|
|
399
|
-
"max_attempts": {
|
|
400
|
-
"type": "number",
|
|
401
|
-
"description": "最大尝试次数(可选,默认3次,用于处理多层弹窗)"
|
|
402
|
-
}
|
|
403
|
-
},
|
|
404
|
-
"required": []
|
|
405
|
-
}
|
|
406
|
-
))
|
|
407
|
-
|
|
408
389
|
# ==================== pytest 脚本生成 ====================
|
|
409
390
|
tools.append(Tool(
|
|
410
391
|
name="mobile_get_operation_history",
|
|
@@ -472,7 +453,12 @@ class MobileMCPServer:
|
|
|
472
453
|
|
|
473
454
|
# 点击
|
|
474
455
|
elif name == "mobile_click_at_coords":
|
|
475
|
-
result = self.tools.click_at_coords(
|
|
456
|
+
result = self.tools.click_at_coords(
|
|
457
|
+
arguments["x"],
|
|
458
|
+
arguments["y"],
|
|
459
|
+
arguments.get("image_width", 0),
|
|
460
|
+
arguments.get("image_height", 0)
|
|
461
|
+
)
|
|
476
462
|
return [TextContent(type="text", text=self.format_response(result))]
|
|
477
463
|
|
|
478
464
|
elif name == "mobile_click_by_text":
|
|
@@ -540,13 +526,6 @@ class MobileMCPServer:
|
|
|
540
526
|
result = self.tools.assert_text(arguments["text"])
|
|
541
527
|
return [TextContent(type="text", text=self.format_response(result))]
|
|
542
528
|
|
|
543
|
-
elif name == "mobile_close_ad":
|
|
544
|
-
result = self.tools.close_ad(
|
|
545
|
-
arguments.get("keywords"),
|
|
546
|
-
arguments.get("max_attempts", 3)
|
|
547
|
-
)
|
|
548
|
-
return [TextContent(type="text", text=self.format_response(result))]
|
|
549
|
-
|
|
550
529
|
# 脚本生成
|
|
551
530
|
elif name == "mobile_get_operation_history":
|
|
552
531
|
result = self.tools.get_operation_history(arguments.get("limit"))
|
|
@@ -25,7 +25,7 @@ if requirements_file.exists():
|
|
|
25
25
|
|
|
26
26
|
setup(
|
|
27
27
|
name="mobile-mcp-ai",
|
|
28
|
-
version="2.3.
|
|
28
|
+
version="2.3.4", # 修复截图坐标偏移问题:支持图片坐标自动转换为屏幕坐标
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|