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