mobile-mcp-ai 2.5.9__py3-none-any.whl → 2.6.5__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/config.py +32 -0
- mobile_mcp/core/basic_tools_lite.py +1672 -697
- mobile_mcp/mcp_tools/mcp_server.py +298 -278
- {mobile_mcp_ai-2.5.9.dist-info → mobile_mcp_ai-2.6.5.dist-info}/METADATA +1 -1
- {mobile_mcp_ai-2.5.9.dist-info → mobile_mcp_ai-2.6.5.dist-info}/RECORD +9 -9
- {mobile_mcp_ai-2.5.9.dist-info → mobile_mcp_ai-2.6.5.dist-info}/WHEEL +0 -0
- {mobile_mcp_ai-2.5.9.dist-info → mobile_mcp_ai-2.6.5.dist-info}/entry_points.txt +0 -0
- {mobile_mcp_ai-2.5.9.dist-info → mobile_mcp_ai-2.6.5.dist-info}/licenses/LICENSE +0 -0
- {mobile_mcp_ai-2.5.9.dist-info → mobile_mcp_ai-2.6.5.dist-info}/top_level.txt +0 -0
|
@@ -102,12 +102,19 @@ class MobileMCPServer:
|
|
|
102
102
|
self.tools = None
|
|
103
103
|
self._initialized = False
|
|
104
104
|
self._last_error = None # 保存最后一次连接失败的错误
|
|
105
|
+
|
|
106
|
+
# Token 优化配置
|
|
107
|
+
try:
|
|
108
|
+
from mobile_mcp.config import Config
|
|
109
|
+
self._compact_desc = Config.COMPACT_TOOL_DESCRIPTION
|
|
110
|
+
except ImportError:
|
|
111
|
+
self._compact_desc = True # 默认开启精简模式
|
|
105
112
|
|
|
106
113
|
@staticmethod
|
|
107
114
|
def format_response(result) -> str:
|
|
108
|
-
"""
|
|
115
|
+
"""统一格式化返回值(Token 优化:无缩进)"""
|
|
109
116
|
if isinstance(result, (dict, list)):
|
|
110
|
-
return json.dumps(result, ensure_ascii=False,
|
|
117
|
+
return json.dumps(result, ensure_ascii=False, separators=(',', ':'))
|
|
111
118
|
return str(result)
|
|
112
119
|
|
|
113
120
|
async def initialize(self):
|
|
@@ -198,42 +205,54 @@ class MobileMCPServer:
|
|
|
198
205
|
return "android"
|
|
199
206
|
|
|
200
207
|
def get_tools(self):
|
|
201
|
-
"""注册 MCP
|
|
208
|
+
"""注册 MCP 工具"""
|
|
202
209
|
tools = []
|
|
203
210
|
|
|
211
|
+
# 根据配置选择精简或完整描述
|
|
212
|
+
compact = getattr(self, '_compact_desc', True)
|
|
213
|
+
|
|
204
214
|
# ==================== 元素定位(优先使用)====================
|
|
215
|
+
if compact:
|
|
216
|
+
desc_list_elements = "📋 【首选】列出页面元素(token低)。返回text/id用于点击,替代截图确认页面状态。"
|
|
217
|
+
else:
|
|
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
|
+
|
|
205
227
|
tools.append(Tool(
|
|
206
228
|
name="mobile_list_elements",
|
|
207
|
-
description=
|
|
208
|
-
"⚠️ 【重要】点击元素前必须先调用此工具!\n"
|
|
209
|
-
"如果元素在控件树中存在,使用 click_by_id 或 click_by_text 定位。\n"
|
|
210
|
-
"只有当此工具返回空或找不到目标元素时,才使用截图+坐标方式。\n\n"
|
|
211
|
-
"📌 控件树定位优势:\n"
|
|
212
|
-
"- 实时检测元素是否存在\n"
|
|
213
|
-
"- 元素消失时会报错,不会误点击\n"
|
|
214
|
-
"- 跨设备兼容性好",
|
|
229
|
+
description=desc_list_elements,
|
|
215
230
|
inputSchema={"type": "object", "properties": {}, "required": []}
|
|
216
231
|
))
|
|
217
232
|
|
|
218
233
|
# ==================== 截图(视觉兜底)====================
|
|
234
|
+
if compact:
|
|
235
|
+
desc_screenshot = "📸 截图(token高~2000)。优先用list_elements(~500)确认页面状态。"
|
|
236
|
+
else:
|
|
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 可获取原图(用于添加模板)")
|
|
244
|
+
|
|
219
245
|
tools.append(Tool(
|
|
220
246
|
name="mobile_take_screenshot",
|
|
221
|
-
description=
|
|
222
|
-
"⚠️ 【推荐使用 mobile_screenshot_with_som 代替!】\n"
|
|
223
|
-
"SoM 截图会给元素标号,AI 可以直接说'点击几号',更精准!\n\n"
|
|
224
|
-
"🎯 本工具仅用于:\n"
|
|
225
|
-
"- 快速确认页面状态(不需要点击时)\n"
|
|
226
|
-
"- 操作后确认结果\n"
|
|
227
|
-
"- compress=false 时可获取原始分辨率截图(用于添加模板)\n\n"
|
|
228
|
-
"💡 如需点击元素,请用 mobile_screenshot_with_som + mobile_click_by_som",
|
|
247
|
+
description=desc_screenshot,
|
|
229
248
|
inputSchema={
|
|
230
249
|
"type": "object",
|
|
231
250
|
"properties": {
|
|
232
|
-
"description": {"type": "string", "description": "
|
|
233
|
-
"compress": {"type": "boolean", "description": "
|
|
234
|
-
"crop_x": {"type": "integer", "description": "
|
|
235
|
-
"crop_y": {"type": "integer", "description": "
|
|
236
|
-
"crop_size": {"type": "integer", "description": "
|
|
251
|
+
"description": {"type": "string", "description": "截图描述"},
|
|
252
|
+
"compress": {"type": "boolean", "description": "是否压缩", "default": True},
|
|
253
|
+
"crop_x": {"type": "integer", "description": "裁剪中心 X"},
|
|
254
|
+
"crop_y": {"type": "integer", "description": "裁剪中心 Y"},
|
|
255
|
+
"crop_size": {"type": "integer", "description": "裁剪大小"}
|
|
237
256
|
},
|
|
238
257
|
"required": []
|
|
239
258
|
}
|
|
@@ -245,36 +264,33 @@ class MobileMCPServer:
|
|
|
245
264
|
inputSchema={"type": "object", "properties": {}, "required": []}
|
|
246
265
|
))
|
|
247
266
|
|
|
267
|
+
if compact:
|
|
268
|
+
desc_som = "📸 SoM截图(token高)。元素有text时优先用list_elements+click_by_text。"
|
|
269
|
+
else:
|
|
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 确认,不要再截图!")
|
|
280
|
+
|
|
248
281
|
tools.append(Tool(
|
|
249
282
|
name="mobile_screenshot_with_som",
|
|
250
|
-
description=
|
|
251
|
-
"【智能标注】给每个可点击元素画框+编号,检测弹窗时额外标注可能的X按钮位置(黄色)。\n"
|
|
252
|
-
"AI 看图直接说'点击 3 号',调用 mobile_click_by_som(3) 即可!\n\n"
|
|
253
|
-
"🎯 优势:\n"
|
|
254
|
-
"- 元素有编号,精准点击不会误触\n"
|
|
255
|
-
"- 自动检测弹窗,标注可能的关闭按钮位置\n"
|
|
256
|
-
"- 适用于所有页面和所有操作\n\n"
|
|
257
|
-
"⚡ 推荐流程:\n"
|
|
258
|
-
"1. 任何需要操作的场景,都先调用此工具\n"
|
|
259
|
-
"2. 看标注图,找到目标元素编号\n"
|
|
260
|
-
"3. 调用 mobile_click_by_som(编号) 精准点击\n"
|
|
261
|
-
"4. 🔴【必须】点击后再次截图确认操作是否成功!",
|
|
283
|
+
description=desc_som,
|
|
262
284
|
inputSchema={"type": "object", "properties": {}, "required": []}
|
|
263
285
|
))
|
|
264
286
|
|
|
265
287
|
tools.append(Tool(
|
|
266
288
|
name="mobile_click_by_som",
|
|
267
|
-
description="🎯 根据
|
|
268
|
-
"配合 mobile_screenshot_with_som 使用。\n"
|
|
269
|
-
"看图后直接说'点击 3 号',调用此函数即可。\n\n"
|
|
270
|
-
"⚠️ 【重要】点击后建议再次截图确认操作是否成功!",
|
|
289
|
+
description="🎯 根据SoM编号点击。配合screenshot_with_som使用。",
|
|
271
290
|
inputSchema={
|
|
272
291
|
"type": "object",
|
|
273
292
|
"properties": {
|
|
274
|
-
"index": {
|
|
275
|
-
"type": "integer",
|
|
276
|
-
"description": "元素编号(从 1 开始,对应截图中的标注数字)"
|
|
277
|
-
}
|
|
293
|
+
"index": {"type": "integer", "description": "元素编号(从1开始)"}
|
|
278
294
|
},
|
|
279
295
|
"required": ["index"]
|
|
280
296
|
}
|
|
@@ -282,89 +298,92 @@ class MobileMCPServer:
|
|
|
282
298
|
|
|
283
299
|
tools.append(Tool(
|
|
284
300
|
name="mobile_screenshot_with_grid",
|
|
285
|
-
description="
|
|
286
|
-
"在截图上绘制网格线和坐标刻度,帮助快速定位元素位置。\n"
|
|
287
|
-
"如果检测到弹窗,会用绿色圆圈标注可能的关闭按钮位置。\n\n"
|
|
288
|
-
"🎯 适用场景:\n"
|
|
289
|
-
"- 需要精确知道某个元素的坐标\n"
|
|
290
|
-
"- 关闭广告弹窗时定位 X 按钮\n"
|
|
291
|
-
"- 元素不在控件树中时的视觉定位\n\n"
|
|
292
|
-
"💡 返回信息:\n"
|
|
293
|
-
"- 带网格标注的截图\n"
|
|
294
|
-
"- 弹窗边界坐标(如果检测到)\n"
|
|
295
|
-
"- 可能的关闭按钮位置列表(带优先级)\n\n"
|
|
296
|
-
"🔴 【必须】点击后必须再次截图确认操作是否成功!",
|
|
301
|
+
description="📸 带网格坐标截图。用于精确定位元素坐标。",
|
|
297
302
|
inputSchema={
|
|
298
303
|
"type": "object",
|
|
299
304
|
"properties": {
|
|
300
|
-
"grid_size": {
|
|
301
|
-
|
|
302
|
-
"description": "网格间距(像素),默认 100。值越小网格越密,建议 50-200"
|
|
303
|
-
},
|
|
304
|
-
"show_popup_hints": {
|
|
305
|
-
"type": "boolean",
|
|
306
|
-
"description": "是否显示弹窗关闭按钮提示位置,默认 true"
|
|
307
|
-
}
|
|
305
|
+
"grid_size": {"type": "integer", "description": "网格间距(px),默认100"},
|
|
306
|
+
"show_popup_hints": {"type": "boolean", "description": "显示弹窗提示"}
|
|
308
307
|
},
|
|
309
308
|
"required": []
|
|
310
309
|
}
|
|
311
310
|
))
|
|
312
311
|
|
|
313
312
|
# ==================== 点击操作 ====================
|
|
313
|
+
if compact:
|
|
314
|
+
desc_click_text = "👆 文本点击(推荐)。verify可验证点击结果,无需截图确认。position可选top/bottom/left/right。"
|
|
315
|
+
else:
|
|
316
|
+
desc_click_text = ("👆 通过文本点击元素(⭐ 最推荐)\n\n"
|
|
317
|
+
"✅ 最稳定的定位方式,跨设备兼容\n"
|
|
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 确认")
|
|
327
|
+
|
|
314
328
|
tools.append(Tool(
|
|
315
329
|
name="mobile_click_by_text",
|
|
316
|
-
description=
|
|
317
|
-
"✅ 实时检测元素是否存在,元素不存在会报错\n"
|
|
318
|
-
"✅ 不会误点击到其他位置\n"
|
|
319
|
-
"📋 使用前先调用 mobile_list_elements 确认元素文本",
|
|
330
|
+
description=desc_click_text,
|
|
320
331
|
inputSchema={
|
|
321
332
|
"type": "object",
|
|
322
333
|
"properties": {
|
|
323
|
-
"text": {"type": "string", "description": "
|
|
334
|
+
"text": {"type": "string", "description": "元素文本"},
|
|
335
|
+
"position": {"type": "string", "description": "位置:top/bottom/left/right"},
|
|
336
|
+
"verify": {"type": "string", "description": "点击后验证的文本(可选)"}
|
|
324
337
|
},
|
|
325
338
|
"required": ["text"]
|
|
326
339
|
}
|
|
327
340
|
))
|
|
328
341
|
|
|
342
|
+
if compact:
|
|
343
|
+
desc_click_id = "👆 通过resource-id点击。index指定第几个(从0开始)。点击后用list_elements确认。"
|
|
344
|
+
else:
|
|
345
|
+
desc_click_id = ("👆 通过 resource-id 点击元素(推荐)\n\n"
|
|
346
|
+
"✅ 稳定的定位方式,元素不存在会报错\n"
|
|
347
|
+
"📋 使用前 list_elements 获取元素 ID\n"
|
|
348
|
+
"📋 点击后 list_elements 确认(❌不要截图确认!)\n"
|
|
349
|
+
"💡 多个相同 ID 时用 index 指定第几个(从 0 开始)")
|
|
350
|
+
|
|
329
351
|
tools.append(Tool(
|
|
330
352
|
name="mobile_click_by_id",
|
|
331
|
-
description=
|
|
332
|
-
"✅ 最稳定的定位方式\n"
|
|
333
|
-
"✅ 实时检测元素是否存在,元素不存在会报错\n"
|
|
334
|
-
"📋 使用前先调用 mobile_list_elements 获取元素 ID\n"
|
|
335
|
-
"💡 当有多个相同 ID 的元素时,用 index 指定第几个(从 0 开始)",
|
|
353
|
+
description=desc_click_id,
|
|
336
354
|
inputSchema={
|
|
337
355
|
"type": "object",
|
|
338
356
|
"properties": {
|
|
339
|
-
"resource_id": {"type": "string", "description": "
|
|
340
|
-
"index": {"type": "integer", "description": "
|
|
357
|
+
"resource_id": {"type": "string", "description": "resource-id"},
|
|
358
|
+
"index": {"type": "integer", "description": "第几个(从0开始)", "default": 0}
|
|
341
359
|
},
|
|
342
360
|
"required": ["resource_id"]
|
|
343
361
|
}
|
|
344
362
|
))
|
|
345
363
|
|
|
364
|
+
if compact:
|
|
365
|
+
desc_click_coords = "👆 坐标点击(兜底)。优先用click_by_text/id,点击后用list_elements确认。"
|
|
366
|
+
else:
|
|
367
|
+
desc_click_coords = ("👆 点击指定坐标(⚠️ 兜底方案)\n\n"
|
|
368
|
+
"❌ 优先使用 click_by_text 或 click_by_id!\n"
|
|
369
|
+
"仅在 list_elements 无法获取元素时使用。\n\n"
|
|
370
|
+
"📐 坐标转换:截图返回的参数直接传入即可\n"
|
|
371
|
+
"📋 点击后用 list_elements 确认(❌不要截图确认!)")
|
|
372
|
+
|
|
346
373
|
tools.append(Tool(
|
|
347
374
|
name="mobile_click_at_coords",
|
|
348
|
-
description=
|
|
349
|
-
"⚠️ 【重要】优先使用 mobile_click_by_id 或 mobile_click_by_text!\n"
|
|
350
|
-
"仅在 mobile_list_elements 无法获取元素时使用此工具。\n\n"
|
|
351
|
-
"⚠️ 【时序限制】截图分析期间页面可能变化:\n"
|
|
352
|
-
"- 坐标是基于截图时刻的,点击时页面可能已不同\n"
|
|
353
|
-
"- 如果误点击,调用 mobile_press_key(back) 返回\n"
|
|
354
|
-
"- 对于定时弹窗(如广告),建议等待其自动消失\n\n"
|
|
355
|
-
"📐 坐标转换:截图返回的 image_width/height 等参数直接传入即可\n\n"
|
|
356
|
-
"🔴 【必须】点击后必须再次截图确认操作是否成功!",
|
|
375
|
+
description=desc_click_coords,
|
|
357
376
|
inputSchema={
|
|
358
377
|
"type": "object",
|
|
359
378
|
"properties": {
|
|
360
|
-
"x": {"type": "number", "description": "X
|
|
361
|
-
"y": {"type": "number", "description": "Y
|
|
362
|
-
"image_width": {"type": "number", "description": "
|
|
363
|
-
"image_height": {"type": "number", "description": "
|
|
364
|
-
"original_img_width": {"type": "number", "description": "
|
|
365
|
-
"original_img_height": {"type": "number", "description": "
|
|
366
|
-
"crop_offset_x": {"type": "number", "description": "
|
|
367
|
-
"crop_offset_y": {"type": "number", "description": "
|
|
379
|
+
"x": {"type": "number", "description": "X 坐标"},
|
|
380
|
+
"y": {"type": "number", "description": "Y 坐标"},
|
|
381
|
+
"image_width": {"type": "number", "description": "图片宽度"},
|
|
382
|
+
"image_height": {"type": "number", "description": "图片高度"},
|
|
383
|
+
"original_img_width": {"type": "number", "description": "原图宽"},
|
|
384
|
+
"original_img_height": {"type": "number", "description": "原图高"},
|
|
385
|
+
"crop_offset_x": {"type": "number", "description": "裁剪X偏移"},
|
|
386
|
+
"crop_offset_y": {"type": "number", "description": "裁剪Y偏移"}
|
|
368
387
|
},
|
|
369
388
|
"required": ["x", "y"]
|
|
370
389
|
}
|
|
@@ -372,15 +391,7 @@ class MobileMCPServer:
|
|
|
372
391
|
|
|
373
392
|
tools.append(Tool(
|
|
374
393
|
name="mobile_click_by_percent",
|
|
375
|
-
description="👆
|
|
376
|
-
"🎯 原理:屏幕左上角是 (0%, 0%),右下角是 (100%, 100%)\n"
|
|
377
|
-
"📐 示例:\n"
|
|
378
|
-
" - (50, 50) = 屏幕正中央\n"
|
|
379
|
-
" - (10, 5) = 左上角附近\n"
|
|
380
|
-
" - (85, 90) = 右下角附近\n\n"
|
|
381
|
-
"✅ 优势:同样的百分比在不同分辨率设备上都能点到相同相对位置\n"
|
|
382
|
-
"💡 录制一次,多设备回放\n\n"
|
|
383
|
-
"🔴 【必须】点击后必须再次截图确认操作是否成功!",
|
|
394
|
+
description="👆 百分比点击。(50,50)=屏幕中心。跨设备兼容。",
|
|
384
395
|
inputSchema={
|
|
385
396
|
"type": "object",
|
|
386
397
|
"properties": {
|
|
@@ -394,15 +405,12 @@ class MobileMCPServer:
|
|
|
394
405
|
# ==================== 长按操作 ====================
|
|
395
406
|
tools.append(Tool(
|
|
396
407
|
name="mobile_long_press_by_id",
|
|
397
|
-
description="👆 通过
|
|
398
|
-
"✅ 最稳定的长按定位方式,跨设备完美兼容\n"
|
|
399
|
-
"📋 使用前请先调用 mobile_list_elements 获取元素 ID\n"
|
|
400
|
-
"💡 生成的脚本使用 d(resourceId='...').long_click() 定位,最稳定",
|
|
408
|
+
description="👆 通过resource-id长按。",
|
|
401
409
|
inputSchema={
|
|
402
410
|
"type": "object",
|
|
403
411
|
"properties": {
|
|
404
|
-
"resource_id": {"type": "string", "description": "
|
|
405
|
-
"duration": {"type": "number", "description": "
|
|
412
|
+
"resource_id": {"type": "string", "description": "resource-id"},
|
|
413
|
+
"duration": {"type": "number", "description": "长按秒数,默认1.0"}
|
|
406
414
|
},
|
|
407
415
|
"required": ["resource_id"]
|
|
408
416
|
}
|
|
@@ -410,15 +418,12 @@ class MobileMCPServer:
|
|
|
410
418
|
|
|
411
419
|
tools.append(Tool(
|
|
412
420
|
name="mobile_long_press_by_text",
|
|
413
|
-
description="👆
|
|
414
|
-
"✅ 优势:跨设备兼容,不受屏幕分辨率影响\n"
|
|
415
|
-
"📋 使用前请先调用 mobile_list_elements 确认元素有文本\n"
|
|
416
|
-
"💡 生成的脚本使用 d(text='...').long_click() 定位,稳定可靠",
|
|
421
|
+
description="👆 通过文本长按。",
|
|
417
422
|
inputSchema={
|
|
418
423
|
"type": "object",
|
|
419
424
|
"properties": {
|
|
420
|
-
"text": {"type": "string", "description": "
|
|
421
|
-
"duration": {"type": "number", "description": "
|
|
425
|
+
"text": {"type": "string", "description": "文本内容"},
|
|
426
|
+
"duration": {"type": "number", "description": "长按秒数,默认1.0"}
|
|
422
427
|
},
|
|
423
428
|
"required": ["text"]
|
|
424
429
|
}
|
|
@@ -426,20 +431,13 @@ class MobileMCPServer:
|
|
|
426
431
|
|
|
427
432
|
tools.append(Tool(
|
|
428
433
|
name="mobile_long_press_by_percent",
|
|
429
|
-
description="👆
|
|
430
|
-
"🎯 原理:屏幕左上角是 (0%, 0%),右下角是 (100%, 100%)\n"
|
|
431
|
-
"📐 示例:\n"
|
|
432
|
-
" - (50, 50) = 屏幕正中央\n"
|
|
433
|
-
" - (10, 5) = 左上角附近\n"
|
|
434
|
-
" - (85, 90) = 右下角附近\n\n"
|
|
435
|
-
"✅ 优势:同样的百分比在不同分辨率设备上都能长按到相同相对位置\n"
|
|
436
|
-
"💡 录制一次,多设备回放",
|
|
434
|
+
description="👆 百分比长按。(50,50)=屏幕中心。",
|
|
437
435
|
inputSchema={
|
|
438
436
|
"type": "object",
|
|
439
437
|
"properties": {
|
|
440
|
-
"x_percent": {"type": "number", "description": "X
|
|
441
|
-
"y_percent": {"type": "number", "description": "Y
|
|
442
|
-
"duration": {"type": "number", "description": "
|
|
438
|
+
"x_percent": {"type": "number", "description": "X百分比(0-100)"},
|
|
439
|
+
"y_percent": {"type": "number", "description": "Y百分比(0-100)"},
|
|
440
|
+
"duration": {"type": "number", "description": "长按秒数,默认1.0"}
|
|
443
441
|
},
|
|
444
442
|
"required": ["x_percent", "y_percent"]
|
|
445
443
|
}
|
|
@@ -447,28 +445,19 @@ class MobileMCPServer:
|
|
|
447
445
|
|
|
448
446
|
tools.append(Tool(
|
|
449
447
|
name="mobile_long_press_at_coords",
|
|
450
|
-
description="👆
|
|
451
|
-
"🎯 仅在以下场景使用:\n"
|
|
452
|
-
"- 游戏(Unity/Cocos)无法获取元素\n"
|
|
453
|
-
"- mobile_list_elements 返回空\n"
|
|
454
|
-
"- 元素没有 id 和 text\n\n"
|
|
455
|
-
"⚠️ 【坐标转换】截图返回的参数直接传入:\n"
|
|
456
|
-
" - image_width/image_height: 压缩后尺寸(AI 看到的)\n"
|
|
457
|
-
" - original_img_width/original_img_height: 原图尺寸(用于转换)\n"
|
|
458
|
-
" - crop_offset_x/crop_offset_y: 局部截图偏移\n\n"
|
|
459
|
-
"✅ 自动记录百分比坐标,生成脚本时转换为跨分辨率兼容的百分比定位",
|
|
448
|
+
description="👆 坐标长按(兜底)。优先用text/id。",
|
|
460
449
|
inputSchema={
|
|
461
450
|
"type": "object",
|
|
462
451
|
"properties": {
|
|
463
|
-
"x": {"type": "number", "description": "X
|
|
464
|
-
"y": {"type": "number", "description": "Y
|
|
465
|
-
"duration": {"type": "number", "description": "
|
|
466
|
-
"image_width": {"type": "number", "description": "
|
|
467
|
-
"image_height": {"type": "number", "description": "
|
|
468
|
-
"original_img_width": {"type": "number", "description": "
|
|
469
|
-
"original_img_height": {"type": "number", "description": "
|
|
470
|
-
"crop_offset_x": {"type": "number", "description": "
|
|
471
|
-
"crop_offset_y": {"type": "number", "description": "
|
|
452
|
+
"x": {"type": "number", "description": "X坐标"},
|
|
453
|
+
"y": {"type": "number", "description": "Y坐标"},
|
|
454
|
+
"duration": {"type": "number", "description": "长按秒数"},
|
|
455
|
+
"image_width": {"type": "number", "description": "图片宽"},
|
|
456
|
+
"image_height": {"type": "number", "description": "图片高"},
|
|
457
|
+
"original_img_width": {"type": "number", "description": "原图宽"},
|
|
458
|
+
"original_img_height": {"type": "number", "description": "原图高"},
|
|
459
|
+
"crop_offset_x": {"type": "number", "description": "裁剪X偏移"},
|
|
460
|
+
"crop_offset_y": {"type": "number", "description": "裁剪Y偏移"}
|
|
472
461
|
},
|
|
473
462
|
"required": ["x", "y"]
|
|
474
463
|
}
|
|
@@ -477,12 +466,12 @@ class MobileMCPServer:
|
|
|
477
466
|
# ==================== 输入操作 ====================
|
|
478
467
|
tools.append(Tool(
|
|
479
468
|
name="mobile_input_text_by_id",
|
|
480
|
-
description="⌨️
|
|
469
|
+
description="⌨️ 通过ID输入文本。",
|
|
481
470
|
inputSchema={
|
|
482
471
|
"type": "object",
|
|
483
472
|
"properties": {
|
|
484
|
-
"resource_id": {"type": "string", "description": "
|
|
485
|
-
"text": {"type": "string", "description": "
|
|
473
|
+
"resource_id": {"type": "string", "description": "resource-id"},
|
|
474
|
+
"text": {"type": "string", "description": "输入文本"}
|
|
486
475
|
},
|
|
487
476
|
"required": ["resource_id", "text"]
|
|
488
477
|
}
|
|
@@ -490,13 +479,13 @@ class MobileMCPServer:
|
|
|
490
479
|
|
|
491
480
|
tools.append(Tool(
|
|
492
481
|
name="mobile_input_at_coords",
|
|
493
|
-
description="⌨️
|
|
482
|
+
description="⌨️ 坐标输入文本。",
|
|
494
483
|
inputSchema={
|
|
495
484
|
"type": "object",
|
|
496
485
|
"properties": {
|
|
497
|
-
"x": {"type": "number", "description": "
|
|
498
|
-
"y": {"type": "number", "description": "
|
|
499
|
-
"text": {"type": "string", "description": "
|
|
486
|
+
"x": {"type": "number", "description": "X坐标"},
|
|
487
|
+
"y": {"type": "number", "description": "Y坐标"},
|
|
488
|
+
"text": {"type": "string", "description": "输入文本"}
|
|
500
489
|
},
|
|
501
490
|
"required": ["x", "y", "text"]
|
|
502
491
|
}
|
|
@@ -505,27 +494,13 @@ class MobileMCPServer:
|
|
|
505
494
|
# ==================== 导航操作 ====================
|
|
506
495
|
tools.append(Tool(
|
|
507
496
|
name="mobile_swipe",
|
|
508
|
-
description="👆
|
|
509
|
-
"💡 左右滑动时,可指定高度坐标或百分比:\n"
|
|
510
|
-
"- y: 指定高度坐标(像素)\n"
|
|
511
|
-
"- y_percent: 指定高度百分比 (0-100)\n"
|
|
512
|
-
"- 两者都未指定时,使用屏幕中心高度",
|
|
497
|
+
description="👆 滑动。方向:up/down/left/right。",
|
|
513
498
|
inputSchema={
|
|
514
499
|
"type": "object",
|
|
515
500
|
"properties": {
|
|
516
|
-
"direction": {
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
"description": "滑动方向"
|
|
520
|
-
},
|
|
521
|
-
"y": {
|
|
522
|
-
"type": "integer",
|
|
523
|
-
"description": "左右滑动时指定的高度坐标(像素,0-屏幕高度)"
|
|
524
|
-
},
|
|
525
|
-
"y_percent": {
|
|
526
|
-
"type": "number",
|
|
527
|
-
"description": "左右滑动时指定的高度百分比 (0-100)"
|
|
528
|
-
}
|
|
501
|
+
"direction": {"type": "string", "enum": ["up", "down", "left", "right"], "description": "方向"},
|
|
502
|
+
"y": {"type": "integer", "description": "左右滑动高度(px)"},
|
|
503
|
+
"y_percent": {"type": "number", "description": "左右滑动高度(%)"}
|
|
529
504
|
},
|
|
530
505
|
"required": ["direction"]
|
|
531
506
|
}
|
|
@@ -533,11 +508,11 @@ class MobileMCPServer:
|
|
|
533
508
|
|
|
534
509
|
tools.append(Tool(
|
|
535
510
|
name="mobile_press_key",
|
|
536
|
-
description="⌨️
|
|
511
|
+
description="⌨️ 按键:home/back/enter/search。",
|
|
537
512
|
inputSchema={
|
|
538
513
|
"type": "object",
|
|
539
514
|
"properties": {
|
|
540
|
-
"key": {"type": "string", "description": "
|
|
515
|
+
"key": {"type": "string", "description": "按键名"}
|
|
541
516
|
},
|
|
542
517
|
"required": ["key"]
|
|
543
518
|
}
|
|
@@ -545,11 +520,11 @@ class MobileMCPServer:
|
|
|
545
520
|
|
|
546
521
|
tools.append(Tool(
|
|
547
522
|
name="mobile_wait",
|
|
548
|
-
description="⏰
|
|
523
|
+
description="⏰ 等待指定秒数。",
|
|
549
524
|
inputSchema={
|
|
550
525
|
"type": "object",
|
|
551
526
|
"properties": {
|
|
552
|
-
"seconds": {"type": "number", "description": "
|
|
527
|
+
"seconds": {"type": "number", "description": "等待秒数"}
|
|
553
528
|
},
|
|
554
529
|
"required": ["seconds"]
|
|
555
530
|
}
|
|
@@ -558,11 +533,11 @@ class MobileMCPServer:
|
|
|
558
533
|
# ==================== 应用管理 ====================
|
|
559
534
|
tools.append(Tool(
|
|
560
535
|
name="mobile_launch_app",
|
|
561
|
-
description="🚀
|
|
536
|
+
description="🚀 启动应用。",
|
|
562
537
|
inputSchema={
|
|
563
538
|
"type": "object",
|
|
564
539
|
"properties": {
|
|
565
|
-
"package_name": {"type": "string", "description": "
|
|
540
|
+
"package_name": {"type": "string", "description": "包名"}
|
|
566
541
|
},
|
|
567
542
|
"required": ["package_name"]
|
|
568
543
|
}
|
|
@@ -574,7 +549,7 @@ class MobileMCPServer:
|
|
|
574
549
|
inputSchema={
|
|
575
550
|
"type": "object",
|
|
576
551
|
"properties": {
|
|
577
|
-
"package_name": {"type": "string", "description": "
|
|
552
|
+
"package_name": {"type": "string", "description": "包名"}
|
|
578
553
|
},
|
|
579
554
|
"required": ["package_name"]
|
|
580
555
|
}
|
|
@@ -582,11 +557,11 @@ class MobileMCPServer:
|
|
|
582
557
|
|
|
583
558
|
tools.append(Tool(
|
|
584
559
|
name="mobile_list_apps",
|
|
585
|
-
description="📦
|
|
560
|
+
description="📦 列出应用。",
|
|
586
561
|
inputSchema={
|
|
587
562
|
"type": "object",
|
|
588
563
|
"properties": {
|
|
589
|
-
"filter": {"type": "string", "description": "
|
|
564
|
+
"filter": {"type": "string", "description": "过滤词"}
|
|
590
565
|
},
|
|
591
566
|
"required": []
|
|
592
567
|
}
|
|
@@ -595,79 +570,129 @@ class MobileMCPServer:
|
|
|
595
570
|
# ==================== 设备管理 ====================
|
|
596
571
|
tools.append(Tool(
|
|
597
572
|
name="mobile_list_devices",
|
|
598
|
-
description="📱
|
|
573
|
+
description="📱 列出设备。",
|
|
599
574
|
inputSchema={"type": "object", "properties": {}, "required": []}
|
|
600
575
|
))
|
|
601
576
|
|
|
602
577
|
tools.append(Tool(
|
|
603
578
|
name="mobile_check_connection",
|
|
604
|
-
description="🔌
|
|
579
|
+
description="🔌 检查连接。",
|
|
605
580
|
inputSchema={"type": "object", "properties": {}, "required": []}
|
|
606
581
|
))
|
|
607
582
|
|
|
608
583
|
# ==================== 辅助工具 ====================
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
584
|
+
if compact:
|
|
585
|
+
desc_find_close = "🔍 查找关闭按钮(只找不点)。返回坐标和推荐的点击命令。"
|
|
586
|
+
else:
|
|
587
|
+
desc_find_close = """🔍 智能查找关闭按钮(只找不点,返回位置)
|
|
588
|
+
|
|
589
|
+
⚡ 【推荐首选】遇到弹窗时优先调用此工具!无需先截图。
|
|
612
590
|
|
|
613
|
-
|
|
591
|
+
从元素树中找最可能的关闭按钮,返回坐标和推荐的点击命令。
|
|
614
592
|
|
|
615
593
|
🎯 识别策略(优先级):
|
|
616
|
-
1. 文本匹配:×、X、关闭、取消、跳过
|
|
617
|
-
2.
|
|
618
|
-
3.
|
|
594
|
+
1. 文本匹配:×、X、关闭、取消、跳过 等(得分100)
|
|
595
|
+
2. resource-id 匹配:包含 close/dismiss/skip(得分95)
|
|
596
|
+
3. content-desc 匹配:包含 close/关闭(得分90)
|
|
597
|
+
4. 小尺寸 clickable 元素(右上角优先,得分70+)
|
|
619
598
|
|
|
620
599
|
✅ 返回内容:
|
|
621
600
|
- 坐标 (x, y) 和百分比 (x%, y%)
|
|
622
|
-
-
|
|
623
|
-
-
|
|
601
|
+
- resource-id(如果有)
|
|
602
|
+
- 推荐的点击命令(优先 click_by_text,其次 click_by_id,最后 click_by_percent)
|
|
624
603
|
|
|
625
604
|
💡 使用流程:
|
|
626
|
-
1.
|
|
627
|
-
2.
|
|
628
|
-
3.
|
|
605
|
+
1. 直接调用此工具(无需先截图/列元素)
|
|
606
|
+
2. 根据返回的 click_command 执行点击
|
|
607
|
+
3. 如果返回 success=false,才需要截图分析"""
|
|
608
|
+
|
|
609
|
+
tools.append(Tool(
|
|
610
|
+
name="mobile_find_close_button",
|
|
611
|
+
description=desc_find_close,
|
|
629
612
|
inputSchema={"type": "object", "properties": {}, "required": []}
|
|
630
613
|
))
|
|
631
614
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
615
|
+
if compact:
|
|
616
|
+
desc_close_popup = "🚫 智能检测并关闭弹窗。自动查找×/关闭/跳过按钮。"
|
|
617
|
+
else:
|
|
618
|
+
desc_close_popup = """🚫 智能检测并关闭弹窗
|
|
635
619
|
|
|
636
|
-
|
|
620
|
+
⚡ 【自动检测】会先检测是否存在弹窗:
|
|
621
|
+
- 如果没有弹窗 → 直接返回"无弹窗",不执行任何操作
|
|
622
|
+
- 如果有弹窗 → 自动查找并点击关闭按钮
|
|
637
623
|
|
|
638
|
-
✅
|
|
639
|
-
|
|
624
|
+
✅ 适用场景:
|
|
625
|
+
- 启动应用后检测并关闭可能出现的弹窗
|
|
626
|
+
- 页面跳转后检测并关闭弹窗
|
|
627
|
+
- 无需先截图确认弹窗是否存在
|
|
640
628
|
|
|
641
|
-
|
|
642
|
-
-
|
|
643
|
-
-
|
|
644
|
-
-
|
|
629
|
+
🎯 检测策略:
|
|
630
|
+
- 查找控件树中的关闭按钮(×、关闭、跳过等)
|
|
631
|
+
- 检测弹窗区域(Dialog/Popup/Alert 等)
|
|
632
|
+
- 查找小尺寸的可点击元素(优先角落位置)
|
|
645
633
|
|
|
646
|
-
🔴
|
|
647
|
-
|
|
634
|
+
🔴 【必须】如果返回已点击,需再次截图确认弹窗是否真的关闭了!"""
|
|
635
|
+
|
|
636
|
+
tools.append(Tool(
|
|
637
|
+
name="mobile_close_popup",
|
|
638
|
+
description=desc_close_popup,
|
|
648
639
|
inputSchema={"type": "object", "properties": {}, "required": []}
|
|
649
640
|
))
|
|
650
641
|
|
|
651
642
|
tools.append(Tool(
|
|
652
643
|
name="mobile_assert_text",
|
|
653
|
-
description="✅
|
|
644
|
+
description="✅ 检查页面是否包含文本。",
|
|
654
645
|
inputSchema={
|
|
655
646
|
"type": "object",
|
|
656
647
|
"properties": {
|
|
657
|
-
"text": {"type": "string", "description": "
|
|
648
|
+
"text": {"type": "string", "description": "文本"}
|
|
658
649
|
},
|
|
659
650
|
"required": ["text"]
|
|
660
651
|
}
|
|
661
652
|
))
|
|
662
653
|
|
|
654
|
+
# ==================== Toast 检测(仅 Android)====================
|
|
655
|
+
tools.append(Tool(
|
|
656
|
+
name="mobile_start_toast_watch",
|
|
657
|
+
description="🔔 开始监听Toast。必须在操作前调用。",
|
|
658
|
+
inputSchema={"type": "object", "properties": {}, "required": []}
|
|
659
|
+
))
|
|
660
|
+
|
|
661
|
+
tools.append(Tool(
|
|
662
|
+
name="mobile_get_toast",
|
|
663
|
+
description="🍞 获取Toast消息。配合start_toast_watch使用。",
|
|
664
|
+
inputSchema={
|
|
665
|
+
"type": "object",
|
|
666
|
+
"properties": {
|
|
667
|
+
"timeout": {"type": "number", "description": "超时秒数,默认5"},
|
|
668
|
+
"reset_first": {"type": "boolean", "description": "清除旧缓存"}
|
|
669
|
+
},
|
|
670
|
+
"required": []
|
|
671
|
+
}
|
|
672
|
+
))
|
|
673
|
+
|
|
674
|
+
tools.append(Tool(
|
|
675
|
+
name="mobile_assert_toast",
|
|
676
|
+
description="✅ 断言Toast内容。",
|
|
677
|
+
inputSchema={
|
|
678
|
+
"type": "object",
|
|
679
|
+
"properties": {
|
|
680
|
+
"expected_text": {"type": "string", "description": "期望文本"},
|
|
681
|
+
"timeout": {"type": "number", "description": "超时秒数"},
|
|
682
|
+
"contains": {"type": "boolean", "description": "包含匹配(默认true)"}
|
|
683
|
+
},
|
|
684
|
+
"required": ["expected_text"]
|
|
685
|
+
}
|
|
686
|
+
))
|
|
687
|
+
|
|
663
688
|
# ==================== pytest 脚本生成 ====================
|
|
664
689
|
tools.append(Tool(
|
|
665
690
|
name="mobile_get_operation_history",
|
|
666
|
-
description="📜
|
|
691
|
+
description="📜 获取操作历史。",
|
|
667
692
|
inputSchema={
|
|
668
693
|
"type": "object",
|
|
669
694
|
"properties": {
|
|
670
|
-
"limit": {"type": "number", "description": "
|
|
695
|
+
"limit": {"type": "number", "description": "条数"}
|
|
671
696
|
},
|
|
672
697
|
"required": []
|
|
673
698
|
}
|
|
@@ -675,61 +700,53 @@ class MobileMCPServer:
|
|
|
675
700
|
|
|
676
701
|
tools.append(Tool(
|
|
677
702
|
name="mobile_clear_operation_history",
|
|
678
|
-
description="🗑️
|
|
679
|
-
"⚠️ 开始新的测试录制前必须调用!\n"
|
|
680
|
-
"📋 录制流程:清空历史 → 执行操作(优先用ID/文本定位)→ 生成脚本",
|
|
703
|
+
description="🗑️ 清空操作历史。录制前调用。",
|
|
681
704
|
inputSchema={"type": "object", "properties": {}, "required": []}
|
|
682
705
|
))
|
|
683
706
|
|
|
684
707
|
tools.append(Tool(
|
|
685
708
|
name="mobile_generate_test_script",
|
|
686
|
-
description="📝 生成
|
|
687
|
-
"⚠️ 【重要】录制操作时请优先使用稳定定位:\n"
|
|
688
|
-
"1️⃣ 先调用 mobile_list_elements 获取元素列表\n"
|
|
689
|
-
"2️⃣ 优先用 mobile_click_by_id(最稳定,跨设备兼容)\n"
|
|
690
|
-
"3️⃣ 其次用 mobile_click_by_text(稳定)\n"
|
|
691
|
-
"4️⃣ 最后才用坐标点击(会自动转百分比,跨分辨率兼容)\n\n"
|
|
692
|
-
"使用流程:\n"
|
|
693
|
-
"1. 清空历史 mobile_clear_operation_history\n"
|
|
694
|
-
"2. 执行操作(优先用 ID/文本定位)\n"
|
|
695
|
-
"3. 调用此工具生成脚本\n"
|
|
696
|
-
"4. 脚本保存到 tests/ 目录\n\n"
|
|
697
|
-
"💡 定位优先级:ID > 文本 > 百分比 > 坐标",
|
|
709
|
+
description="📝 生成pytest脚本。基于操作历史生成。",
|
|
698
710
|
inputSchema={
|
|
699
711
|
"type": "object",
|
|
700
712
|
"properties": {
|
|
701
|
-
"test_name": {"type": "string", "description": "
|
|
702
|
-
"package_name": {"type": "string", "description": "
|
|
703
|
-
"filename": {"type": "string", "description": "
|
|
713
|
+
"test_name": {"type": "string", "description": "用例名"},
|
|
714
|
+
"package_name": {"type": "string", "description": "包名"},
|
|
715
|
+
"filename": {"type": "string", "description": "文件名(不含.py)"}
|
|
704
716
|
},
|
|
705
717
|
"required": ["test_name", "package_name", "filename"]
|
|
706
718
|
}
|
|
707
719
|
))
|
|
708
720
|
|
|
709
721
|
# ==================== 广告弹窗关闭工具 ====================
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
722
|
+
if compact:
|
|
723
|
+
desc_close_ad = "🚫 智能关闭广告弹窗。优先级:控件树→截图AI→模板匹配。"
|
|
724
|
+
else:
|
|
725
|
+
desc_close_ad = """🚫 【推荐】智能检测并关闭广告弹窗
|
|
713
726
|
|
|
714
|
-
|
|
727
|
+
⚡ 【自动检测】会先检测是否存在弹窗:
|
|
728
|
+
- 如果没有弹窗 → 直接返回"无弹窗",不执行任何操作
|
|
729
|
+
- 如果有弹窗 → 自动按优先级尝试关闭
|
|
715
730
|
|
|
731
|
+
🎯 关闭策略(优先级从高到低):
|
|
716
732
|
1️⃣ **控件树查找**(最可靠)
|
|
717
|
-
-
|
|
718
|
-
-
|
|
733
|
+
- 查找文本"关闭"、"跳过"、"×"等
|
|
734
|
+
- 查找 resource-id 包含 close/dismiss
|
|
735
|
+
|
|
736
|
+
2️⃣ **截图 AI 分析**(次优)
|
|
737
|
+
- 返回 SoM 标注截图供 AI 视觉分析
|
|
738
|
+
- AI 找到 X 按钮后用 click_by_som(编号) 点击
|
|
719
739
|
|
|
720
|
-
|
|
740
|
+
3️⃣ **模板匹配**(兜底)
|
|
721
741
|
- 用 OpenCV 匹配已保存的 X 按钮模板
|
|
722
|
-
- 需要积累模板库,模板越多成功率越高
|
|
723
|
-
|
|
724
|
-
3️⃣ **返回截图供 AI 分析**(兜底)
|
|
725
|
-
- 如果前两步失败,返回截图
|
|
726
|
-
- AI 分析后用 mobile_click_by_percent 点击
|
|
727
|
-
- 点击成功后用 mobile_template_add 添加模板(自动学习)
|
|
728
742
|
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
743
|
+
✅ 适用场景:
|
|
744
|
+
- 启动应用后检测并关闭可能出现的广告
|
|
745
|
+
- 无需先截图确认弹窗是否存在"""
|
|
746
|
+
|
|
747
|
+
tools.append(Tool(
|
|
748
|
+
name="mobile_close_ad",
|
|
749
|
+
description=desc_close_ad,
|
|
733
750
|
inputSchema={
|
|
734
751
|
"type": "object",
|
|
735
752
|
"properties": {},
|
|
@@ -739,19 +756,12 @@ class MobileMCPServer:
|
|
|
739
756
|
|
|
740
757
|
tools.append(Tool(
|
|
741
758
|
name="mobile_template_close",
|
|
742
|
-
description="
|
|
743
|
-
|
|
744
|
-
只用 OpenCV 模板匹配,不走控件树。
|
|
745
|
-
一般建议用 mobile_close_ad 代替(会自动先查控件树)。
|
|
746
|
-
|
|
747
|
-
⚙️ 参数:
|
|
748
|
-
- click: 是否点击,默认 true
|
|
749
|
-
- threshold: 匹配阈值 0-1,默认 0.75""",
|
|
759
|
+
description="🎯 模板匹配关闭弹窗。",
|
|
750
760
|
inputSchema={
|
|
751
761
|
"type": "object",
|
|
752
762
|
"properties": {
|
|
753
|
-
"click": {"type": "boolean", "description": "
|
|
754
|
-
"threshold": {"type": "number", "description": "
|
|
763
|
+
"click": {"type": "boolean", "description": "是否点击"},
|
|
764
|
+
"threshold": {"type": "number", "description": "阈值0-1"}
|
|
755
765
|
},
|
|
756
766
|
"required": []
|
|
757
767
|
}
|
|
@@ -759,32 +769,19 @@ class MobileMCPServer:
|
|
|
759
769
|
|
|
760
770
|
tools.append(Tool(
|
|
761
771
|
name="mobile_template_add",
|
|
762
|
-
description="
|
|
763
|
-
|
|
764
|
-
遇到新样式 X 号时,截图并添加到模板库。
|
|
765
|
-
|
|
766
|
-
⚙️ 两种方式(二选一):
|
|
767
|
-
1. 百分比定位(推荐):提供 x_percent, y_percent, size
|
|
768
|
-
2. 像素定位:提供 screenshot_path, x, y, width, height
|
|
769
|
-
|
|
770
|
-
📋 流程:
|
|
771
|
-
1. mobile_screenshot_with_grid 查看 X 号位置
|
|
772
|
-
2. 调用此工具添加模板
|
|
773
|
-
3. 下次同样 X 号就能自动匹配
|
|
774
|
-
|
|
775
|
-
💡 百分比示例:X 在右上角 → x_percent=85, y_percent=12, size=80""",
|
|
772
|
+
description="➕ 添加X号模板。",
|
|
776
773
|
inputSchema={
|
|
777
774
|
"type": "object",
|
|
778
775
|
"properties": {
|
|
779
|
-
"template_name": {"type": "string", "description": "
|
|
780
|
-
"x_percent": {"type": "number", "description": "X
|
|
781
|
-
"y_percent": {"type": "number", "description": "
|
|
782
|
-
"size": {"type": "integer", "description": "
|
|
783
|
-
"screenshot_path": {"type": "string", "description": "
|
|
784
|
-
"x": {"type": "integer", "description": "
|
|
785
|
-
"y": {"type": "integer", "description": "
|
|
786
|
-
"width": {"type": "integer", "description": "
|
|
787
|
-
"height": {"type": "integer", "description": "
|
|
776
|
+
"template_name": {"type": "string", "description": "模板名"},
|
|
777
|
+
"x_percent": {"type": "number", "description": "X百分比"},
|
|
778
|
+
"y_percent": {"type": "number", "description": "Y百分比"},
|
|
779
|
+
"size": {"type": "integer", "description": "裁剪大小(px)"},
|
|
780
|
+
"screenshot_path": {"type": "string", "description": "截图路径"},
|
|
781
|
+
"x": {"type": "integer", "description": "左上X"},
|
|
782
|
+
"y": {"type": "integer", "description": "左上Y"},
|
|
783
|
+
"width": {"type": "integer", "description": "宽"},
|
|
784
|
+
"height": {"type": "integer", "description": "高"}
|
|
788
785
|
},
|
|
789
786
|
"required": ["template_name"]
|
|
790
787
|
}
|
|
@@ -831,7 +828,7 @@ class MobileMCPServer:
|
|
|
831
828
|
elif name == "mobile_screenshot_with_grid":
|
|
832
829
|
result = self.tools.take_screenshot_with_grid(
|
|
833
830
|
grid_size=arguments.get("grid_size", 100),
|
|
834
|
-
show_popup_hints=arguments.get("show_popup_hints",
|
|
831
|
+
show_popup_hints=arguments.get("show_popup_hints", False)
|
|
835
832
|
)
|
|
836
833
|
return [TextContent(type="text", text=self.format_response(result))]
|
|
837
834
|
|
|
@@ -858,7 +855,11 @@ class MobileMCPServer:
|
|
|
858
855
|
return [TextContent(type="text", text=self.format_response(result))]
|
|
859
856
|
|
|
860
857
|
elif name == "mobile_click_by_text":
|
|
861
|
-
result = self.tools.click_by_text(
|
|
858
|
+
result = self.tools.click_by_text(
|
|
859
|
+
arguments["text"],
|
|
860
|
+
position=arguments.get("position"),
|
|
861
|
+
verify=arguments.get("verify")
|
|
862
|
+
)
|
|
862
863
|
return [TextContent(type="text", text=self.format_response(result))]
|
|
863
864
|
|
|
864
865
|
elif name == "mobile_click_by_id":
|
|
@@ -974,6 +975,25 @@ class MobileMCPServer:
|
|
|
974
975
|
result = self.tools.assert_text(arguments["text"])
|
|
975
976
|
return [TextContent(type="text", text=self.format_response(result))]
|
|
976
977
|
|
|
978
|
+
# Toast 检测(仅 Android)
|
|
979
|
+
elif name == "mobile_start_toast_watch":
|
|
980
|
+
result = self.tools.start_toast_watch()
|
|
981
|
+
return [TextContent(type="text", text=self.format_response(result))]
|
|
982
|
+
|
|
983
|
+
elif name == "mobile_get_toast":
|
|
984
|
+
timeout = arguments.get("timeout", 5.0)
|
|
985
|
+
reset_first = arguments.get("reset_first", False)
|
|
986
|
+
result = self.tools.get_toast(timeout=timeout, reset_first=reset_first)
|
|
987
|
+
return [TextContent(type="text", text=self.format_response(result))]
|
|
988
|
+
|
|
989
|
+
elif name == "mobile_assert_toast":
|
|
990
|
+
result = self.tools.assert_toast(
|
|
991
|
+
expected_text=arguments["expected_text"],
|
|
992
|
+
timeout=arguments.get("timeout", 5.0),
|
|
993
|
+
contains=arguments.get("contains", True)
|
|
994
|
+
)
|
|
995
|
+
return [TextContent(type="text", text=self.format_response(result))]
|
|
996
|
+
|
|
977
997
|
# 脚本生成
|
|
978
998
|
elif name == "mobile_get_operation_history":
|
|
979
999
|
result = self.tools.get_operation_history(arguments.get("limit"))
|