mobile-mcp-ai 2.1.2__py3-none-any.whl → 2.5.8__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.
Files changed (65) hide show
  1. mobile_mcp/__init__.py +34 -0
  2. mobile_mcp/config.py +142 -0
  3. mobile_mcp/core/basic_tools_lite.py +3266 -0
  4. {core → mobile_mcp/core}/device_manager.py +2 -2
  5. mobile_mcp/core/dynamic_config.py +272 -0
  6. mobile_mcp/core/ios_client_wda.py +569 -0
  7. mobile_mcp/core/ios_device_manager_wda.py +306 -0
  8. {core → mobile_mcp/core}/mobile_client.py +279 -39
  9. mobile_mcp/core/template_matcher.py +429 -0
  10. mobile_mcp/core/templates/close_buttons/auto_x_0112_151217.png +0 -0
  11. mobile_mcp/core/templates/close_buttons/auto_x_0112_152037.png +0 -0
  12. mobile_mcp/core/templates/close_buttons/auto_x_0112_152840.png +0 -0
  13. mobile_mcp/core/templates/close_buttons/auto_x_0112_153256.png +0 -0
  14. mobile_mcp/core/templates/close_buttons/auto_x_0112_154847.png +0 -0
  15. mobile_mcp/core/templates/close_buttons/gray_x_stock_ad.png +0 -0
  16. {core → mobile_mcp/core}/utils/smart_wait.py +3 -3
  17. mobile_mcp/mcp_tools/__init__.py +10 -0
  18. mobile_mcp/mcp_tools/mcp_server.py +1071 -0
  19. mobile_mcp_ai-2.5.8.dist-info/METADATA +469 -0
  20. mobile_mcp_ai-2.5.8.dist-info/RECORD +32 -0
  21. mobile_mcp_ai-2.5.8.dist-info/entry_points.txt +2 -0
  22. mobile_mcp_ai-2.5.8.dist-info/licenses/LICENSE +201 -0
  23. mobile_mcp_ai-2.5.8.dist-info/top_level.txt +1 -0
  24. core/ai/__init__.py +0 -11
  25. core/ai/ai_analyzer.py +0 -197
  26. core/ai/ai_config.py +0 -116
  27. core/ai/ai_platform_adapter.py +0 -399
  28. core/ai/smart_test_executor.py +0 -520
  29. core/ai/test_generator.py +0 -365
  30. core/ai/test_generator_from_history.py +0 -391
  31. core/ai/test_generator_standalone.py +0 -293
  32. core/assertion/__init__.py +0 -9
  33. core/assertion/smart_assertion.py +0 -341
  34. core/basic_tools.py +0 -377
  35. core/h5/__init__.py +0 -10
  36. core/h5/h5_handler.py +0 -548
  37. core/ios_client.py +0 -219
  38. core/ios_device_manager.py +0 -252
  39. core/locator/__init__.py +0 -10
  40. core/locator/cursor_ai_auto_analyzer.py +0 -119
  41. core/locator/cursor_vision_helper.py +0 -414
  42. core/locator/mobile_smart_locator.py +0 -1640
  43. core/locator/position_analyzer.py +0 -813
  44. core/locator/script_updater.py +0 -157
  45. core/nl_test_runner.py +0 -585
  46. core/smart_app_launcher.py +0 -334
  47. core/smart_tools.py +0 -311
  48. mcp/__init__.py +0 -8
  49. mcp/mcp_server.py +0 -1919
  50. mcp/mcp_server_simple.py +0 -476
  51. mobile_mcp_ai-2.1.2.dist-info/METADATA +0 -567
  52. mobile_mcp_ai-2.1.2.dist-info/RECORD +0 -45
  53. mobile_mcp_ai-2.1.2.dist-info/entry_points.txt +0 -2
  54. mobile_mcp_ai-2.1.2.dist-info/top_level.txt +0 -4
  55. vision/__init__.py +0 -10
  56. vision/vision_locator.py +0 -404
  57. {core → mobile_mcp/core}/__init__.py +0 -0
  58. {core → mobile_mcp/core}/utils/__init__.py +0 -0
  59. {core → mobile_mcp/core}/utils/logger.py +0 -0
  60. {core → mobile_mcp/core}/utils/operation_history_manager.py +0 -0
  61. {utils → mobile_mcp/utils}/__init__.py +0 -0
  62. {utils → mobile_mcp/utils}/logger.py +0 -0
  63. {utils → mobile_mcp/utils}/xml_formatter.py +0 -0
  64. {utils → mobile_mcp/utils}/xml_parser.py +0 -0
  65. {mobile_mcp_ai-2.1.2.dist-info → mobile_mcp_ai-2.5.8.dist-info}/WHEEL +0 -0
@@ -1,334 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """
4
- 智能App启动器 - 处理广告、弹窗、加载等待
5
- """
6
- import asyncio
7
- import sys
8
- from typing import Dict, Optional
9
-
10
-
11
- class SmartAppLauncher:
12
- """
13
- 智能App启动器
14
-
15
- 功能:
16
- 1. 启动App后智能等待主页加载
17
- 2. 自动检测并关闭广告/弹窗
18
- 3. 等待网络加载完成
19
- 4. 智能判断是否进入主页
20
- """
21
-
22
- def __init__(self, mobile_client):
23
- """
24
- 初始化智能启动器
25
-
26
- Args:
27
- mobile_client: MobileClient实例
28
- """
29
- self.client = mobile_client
30
-
31
- # 常见的广告/弹窗关闭按钮特征
32
- self.ad_close_keywords = [
33
- '跳过', '关闭', '×', 'X', 'x', '✕',
34
- 'skip', 'close', '稍后', '取消',
35
- '我知道了', '不再提示', '下次再说',
36
- '暂不', '以后再说', '返回'
37
- ]
38
-
39
- # 常见的弹窗容器特征
40
- self.popup_keywords = [
41
- 'dialog', 'popup', 'alert', 'modal',
42
- '弹窗', '对话框', '提示'
43
- ]
44
-
45
- async def launch_with_smart_wait(
46
- self,
47
- package_name: str,
48
- max_wait: int = 5, # 优化:从10秒减少到5秒
49
- auto_close_ads: bool = True
50
- ) -> Dict:
51
- """
52
- 智能启动App并等待主页加载
53
-
54
- Args:
55
- package_name: App包名
56
- max_wait: 最大等待时间(秒,默认5秒)
57
- auto_close_ads: 是否自动关闭广告/弹窗
58
-
59
- Returns:
60
- 启动结果
61
- """
62
- print(f"\n🚀 智能启动App: {package_name}", file=sys.stderr)
63
-
64
- try:
65
- # 🎯 启动前:强制恢复竖屏(防止上次横屏残留)
66
- print(f" 🔄 检查屏幕方向...", file=sys.stderr)
67
- self.client.force_portrait()
68
-
69
- # 1. 启动App
70
- print(f" 📱 正在启动...", file=sys.stderr)
71
- self.client.u2.app_start(package_name)
72
- await asyncio.sleep(1) # 等待App进程启动
73
-
74
- # 🎯 启动后:再次强制竖屏(防止App启动时强制横屏)
75
- self.client.force_portrait()
76
-
77
- # 2. 验证App是否启动
78
- current_package = await self._get_current_package()
79
- if current_package != package_name:
80
- return {
81
- "success": False,
82
- "reason": f"App启动失败,当前: {current_package},期望: {package_name}"
83
- }
84
-
85
- print(f" ✅ App进程已启动", file=sys.stderr)
86
-
87
- # 3. 智能等待主页加载(检测广告、弹窗、加载状态)
88
- result = await self._wait_for_home_page(
89
- package_name,
90
- max_wait=max_wait,
91
- auto_close_ads=auto_close_ads
92
- )
93
-
94
- if result['loaded']:
95
- print(f" ✅ 主页加载完成!", file=sys.stderr)
96
- return {
97
- "success": True,
98
- "package": package_name,
99
- "wait_time": result['wait_time'],
100
- "ads_closed": result['ads_closed'],
101
- "popups_closed": result['popups_closed']
102
- }
103
- else:
104
- print(f" ⚠️ 等待超时,但App已启动", file=sys.stderr)
105
- return {
106
- "success": True,
107
- "package": package_name,
108
- "warning": "主页加载超时,但App已启动",
109
- "wait_time": result['wait_time'],
110
- "ads_closed": result['ads_closed'],
111
- "popups_closed": result['popups_closed']
112
- }
113
-
114
- except Exception as e:
115
- print(f" ❌ 智能启动失败: {e}", file=sys.stderr)
116
- return {
117
- "success": False,
118
- "reason": str(e)
119
- }
120
-
121
- async def _wait_for_home_page(
122
- self,
123
- package_name: str,
124
- max_wait: int = 5, # 优化:从10秒减少到5秒
125
- auto_close_ads: bool = True
126
- ) -> Dict:
127
- """
128
- 等待主页加载完成
129
-
130
- 策略:
131
- 1. 每0.5秒检查一次页面状态
132
- 2. 检测广告/弹窗并自动关闭
133
- 3. 检测页面是否稳定(元素不再变化)
134
- 4. 超时后返回当前状态
135
-
136
- Returns:
137
- {
138
- "loaded": bool, # 是否加载完成
139
- "wait_time": float, # 等待时间
140
- "ads_closed": int, # 关闭的广告数
141
- "popups_closed": int # 关闭的弹窗数
142
- }
143
- """
144
- import time
145
- start_time = time.time()
146
-
147
- ads_closed = 0
148
- popups_closed = 0
149
- last_snapshot = None
150
- stable_count = 0 # 页面稳定计数(连续2次快照相同认为稳定)
151
-
152
- print(f" ⏳ 等待主页加载(最多{max_wait}秒)...", file=sys.stderr)
153
-
154
- check_interval = 0.3 # 优化:每0.3秒检查一次(更快响应)
155
- max_checks = int(max_wait / check_interval)
156
-
157
- for i in range(max_checks):
158
- await asyncio.sleep(check_interval)
159
- elapsed = time.time() - start_time
160
-
161
- # 检查当前包名(防止跳转到其他App)
162
- current_package = await self._get_current_package()
163
- if current_package != package_name:
164
- print(f" ⚠️ 检测到包名变化: {package_name} -> {current_package}", file=sys.stderr)
165
- # 可能跳转到其他页面(如授权页),继续等待
166
- await asyncio.sleep(1)
167
- continue
168
-
169
- # 获取页面快照
170
- try:
171
- snapshot = self.client.u2.dump_hierarchy()
172
-
173
- # 1. 检测并关闭广告/弹窗
174
- if auto_close_ads:
175
- closed = await self._try_close_ads_and_popups(snapshot)
176
- if closed:
177
- ads_closed += closed
178
- print(f" 🎯 已关闭 {closed} 个广告/弹窗", file=sys.stderr)
179
- await asyncio.sleep(0.5) # 等待关闭动画
180
- continue # 重新检查
181
-
182
- # 2. 检测页面是否稳定
183
- if last_snapshot and snapshot == last_snapshot:
184
- stable_count += 1
185
- if stable_count >= 2:
186
- # 页面已稳定(连续2次快照相同)
187
- print(f" ✅ 页面稳定,加载完成(耗时{elapsed:.1f}秒)", file=sys.stderr)
188
- return {
189
- "loaded": True,
190
- "wait_time": elapsed,
191
- "ads_closed": ads_closed,
192
- "popups_closed": popups_closed
193
- }
194
- else:
195
- stable_count = 0
196
-
197
- last_snapshot = snapshot
198
-
199
- # 优化:每1.5秒打印一次等待进度(从2秒减少)
200
- if i % 5 == 0 and i > 0: # 5 * 0.3秒 = 1.5秒
201
- print(f" ⏳ 等待中... ({elapsed:.1f}秒)", file=sys.stderr)
202
-
203
- except Exception as e:
204
- print(f" ⚠️ 检查页面状态失败: {e}", file=sys.stderr)
205
- continue
206
-
207
- # 超时
208
- elapsed = time.time() - start_time
209
- print(f" ⏰ 等待超时({elapsed:.1f}秒),但App已启动", file=sys.stderr)
210
- return {
211
- "loaded": False,
212
- "wait_time": elapsed,
213
- "ads_closed": ads_closed,
214
- "popups_closed": popups_closed
215
- }
216
-
217
- async def _try_close_ads_and_popups(self, snapshot: str) -> int:
218
- """
219
- 尝试关闭广告和弹窗(更谨慎的检测逻辑)
220
-
221
- Args:
222
- snapshot: 页面XML快照
223
-
224
- Returns:
225
- 关闭的数量
226
- """
227
- closed_count = 0
228
-
229
- try:
230
- # 解析XML查找关闭按钮
231
- elements = self.client.xml_parser.parse(snapshot)
232
-
233
- # 🎯 改进:先检测是否有弹窗容器(避免误点击正常UI)
234
- has_popup = False
235
- for elem in elements:
236
- class_name = elem.get('class', '').lower()
237
- resource_id = elem.get('resource_id', '').lower()
238
- # 检查是否是弹窗容器
239
- if any(keyword in class_name or keyword in resource_id
240
- for keyword in ['dialog', 'popup', 'alert', 'modal']):
241
- has_popup = True
242
- break
243
-
244
- # 如果没有检测到弹窗容器,不执行关闭操作(避免误点击)
245
- if not has_popup:
246
- return 0
247
-
248
- # 查找可能的关闭按钮
249
- close_buttons = []
250
-
251
- for elem in elements:
252
- if not elem.get('clickable', False):
253
- continue
254
-
255
- text = elem.get('text', '').lower()
256
- content_desc = elem.get('content_desc', '').lower()
257
- resource_id = elem.get('resource_id', '').lower()
258
- bounds = elem.get('bounds', '')
259
-
260
- # 检查是否是关闭按钮
261
- is_close_button = False
262
- for keyword in self.ad_close_keywords:
263
- keyword_lower = keyword.lower()
264
- if (keyword_lower in text or
265
- keyword_lower in content_desc or
266
- keyword_lower in resource_id or
267
- ('close' in resource_id and 'btn' in resource_id) or
268
- ('skip' in resource_id)):
269
- is_close_button = True
270
- break
271
-
272
- # 🎯 改进:优先选择右上角的关闭按钮(更可能是真正的关闭按钮)
273
- if is_close_button and bounds:
274
- import re
275
- match = re.search(r'\[(\d+),(\d+)\]\[(\d+),(\d+)\]', bounds)
276
- if match:
277
- x1, y1, x2, y2 = map(int, match.groups())
278
- # 计算元素位置(右上角区域优先级更高)
279
- elem['_priority'] = 0
280
- if x1 > 800: # 右侧
281
- elem['_priority'] += 2
282
- if y1 < 500: # 上部
283
- elem['_priority'] += 2
284
- close_buttons.append(elem)
285
-
286
-
287
- # 🎯 改进:按优先级排序(右上角的关闭按钮优先)
288
- close_buttons.sort(key=lambda x: x.get('_priority', 0), reverse=True)
289
-
290
- # 尝试点击关闭按钮(最多尝试1个,避免误点击)
291
- for button in close_buttons[:1]: # 🎯 改进:只点击优先级最高的1个
292
- try:
293
- # 优先使用bounds点击(更可靠)
294
- bounds = button.get('bounds', '')
295
- if bounds:
296
- # 解析bounds并点击中心点
297
- import re
298
- match = re.search(r'\[(\d+),(\d+)\]\[(\d+),(\d+)\]', bounds)
299
- if match:
300
- x1, y1, x2, y2 = map(int, match.groups())
301
- center_x = (x1 + x2) // 2
302
- center_y = (y1 + y2) // 2
303
-
304
- button_desc = button.get('text') or button.get('content_desc') or '未知'
305
- print(f" 🎯 检测到弹窗,准备点击关闭按钮: {button_desc} (位置: {center_x}, {center_y})", file=sys.stderr)
306
-
307
- # 🎯 改进:延迟1秒再点击(给用户时间看清楚)
308
- await asyncio.sleep(1.0)
309
-
310
- self.client.u2.click(center_x, center_y)
311
- closed_count += 1
312
-
313
- print(f" ✅ 已关闭弹窗: {button_desc}", file=sys.stderr)
314
-
315
- await asyncio.sleep(0.5) # 等待关闭动画
316
-
317
- except Exception as e:
318
- print(f" ⚠️ 点击关闭按钮失败: {e}", file=sys.stderr)
319
- continue
320
-
321
- return closed_count
322
-
323
- except Exception as e:
324
- print(f" ⚠️ 关闭广告/弹窗失败: {e}", file=sys.stderr)
325
- return 0
326
-
327
- async def _get_current_package(self) -> Optional[str]:
328
- """获取当前包名"""
329
- try:
330
- info = self.client.u2.app_current()
331
- return info.get('package')
332
- except:
333
- return None
334
-
core/smart_tools.py DELETED
@@ -1,311 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """
4
- 智能 MCP 工具 - 需要 AI 密钥(可选功能)
5
-
6
- 提供智能定位和分析功能:
7
- - 自然语言元素定位
8
- - 智能元素识别
9
- - 复杂场景分析
10
-
11
- ⚠️ 这些功能需要配置 AI 密钥才能使用
12
- """
13
-
14
- from typing import Dict, Optional
15
- import os
16
-
17
-
18
- class SmartMobileTools:
19
- """智能移动端工具(需要 AI 密钥)"""
20
-
21
- def __init__(self, mobile_client):
22
- """
23
- 初始化智能工具
24
-
25
- Args:
26
- mobile_client: MobileClient 实例
27
- """
28
- self.client = mobile_client
29
- self.ai_available = self._check_ai_available()
30
-
31
- if self.ai_available:
32
- # 延迟导入,避免没有配置 AI 时报错
33
- from .locator.mobile_smart_locator import MobileSmartLocator
34
- self.smart_locator = MobileSmartLocator(mobile_client)
35
- else:
36
- self.smart_locator = None
37
-
38
- def _check_ai_available(self) -> bool:
39
- """检查 AI 是否可用(是否配置了 AI 密钥)"""
40
- try:
41
- from dotenv import load_dotenv
42
- load_dotenv()
43
-
44
- ai_provider = os.getenv('AI_PROVIDER', '')
45
-
46
- # 检查是否配置了任何 AI 提供商
47
- if ai_provider in ['qwen', 'openai', 'claude', 'ollama']:
48
- # 检查对应的 API Key
49
- if ai_provider == 'qwen' and os.getenv('QWEN_API_KEY'):
50
- return True
51
- elif ai_provider == 'openai' and os.getenv('OPENAI_API_KEY'):
52
- return True
53
- elif ai_provider == 'claude' and os.getenv('ANTHROPIC_API_KEY'):
54
- return True
55
- elif ai_provider == 'ollama':
56
- return True # Ollama 不需要 API Key
57
-
58
- return False
59
- except:
60
- return False
61
-
62
- def _ensure_ai_available(self):
63
- """确保 AI 可用,否则抛出友好的错误提示"""
64
- if not self.ai_available:
65
- raise ValueError(
66
- "❌ 智能定位功能需要配置 AI 密钥!\n\n"
67
- "请选择以下方案之一:\n\n"
68
- "方案1:使用基础工具(推荐,不需要 AI)\n"
69
- " - mobile_list_elements() - 列出所有元素\n"
70
- " - mobile_click_by_id(resource_id) - 通过 ID 点击\n"
71
- " - mobile_click_at_coords(x, y) - 通过坐标点击\n\n"
72
- "方案2:配置 AI 密钥(启用智能功能)\n"
73
- " 创建 .env 文件:\n"
74
- " AI_PROVIDER=qwen\n"
75
- " QWEN_API_KEY=your-api-key\n\n"
76
- "详见: backend/mobile_mcp/AI_SETUP.md"
77
- )
78
-
79
- async def smart_click(self, description: str) -> Dict:
80
- """
81
- 智能定位并点击元素(需要 AI 密钥)
82
-
83
- Args:
84
- description: 元素的自然语言描述(如 "顶部搜索框"、"登录按钮")
85
-
86
- Returns:
87
- {"success": true/false, "message": "...", "method": "..."}
88
-
89
- 示例:
90
- # 需要先配置 AI 密钥
91
- result = await tools.smart_click("右上角的设置按钮")
92
-
93
- ⚠️ 如果没有配置 AI 密钥,请使用基础工具:
94
- elements = mobile_list_elements()
95
- mobile_click_by_id("com.app:id/settings")
96
- """
97
- self._ensure_ai_available()
98
-
99
- try:
100
- # 使用智能定位器
101
- result = await self.smart_locator.locate(description)
102
-
103
- if result and result.get('ref'):
104
- # 执行点击
105
- ref = result['ref']
106
- method = result.get('method', 'unknown')
107
-
108
- # 根据不同的 ref 类型执行点击
109
- if ref.startswith('[') and ']' in ref:
110
- # bounds 坐标
111
- import re
112
- coords = re.findall(r'\[(\d+),(\d+)\]', ref)
113
- if coords:
114
- x1, y1 = int(coords[0][0]), int(coords[0][1])
115
- x2, y2 = int(coords[1][0]), int(coords[1][1])
116
- x, y = (x1 + x2) // 2, (y1 + y2) // 2
117
- self.client.u2.click(x, y)
118
- return {
119
- "success": True,
120
- "message": f"智能定位成功: {description}",
121
- "method": method,
122
- "ref": ref
123
- }
124
- elif ':id/' in ref:
125
- # resource-id
126
- self.client.u2(resourceId=ref).click()
127
- return {
128
- "success": True,
129
- "message": f"智能定位成功: {description}",
130
- "method": method,
131
- "ref": ref
132
- }
133
- else:
134
- # text
135
- self.client.u2(text=ref).click()
136
- return {
137
- "success": True,
138
- "message": f"智能定位成功: {description}",
139
- "method": method,
140
- "ref": ref
141
- }
142
- else:
143
- return {
144
- "success": False,
145
- "message": f"智能定位失败: {description}",
146
- "suggestion": "请使用 mobile_list_elements() 查看页面元素,然后使用 mobile_click_by_id()"
147
- }
148
- except Exception as e:
149
- return {
150
- "success": False,
151
- "message": f"智能点击失败: {str(e)}",
152
- "suggestion": "建议使用基础工具: mobile_list_elements() + mobile_click_by_id()"
153
- }
154
-
155
- async def smart_input(self, description: str, text: str) -> Dict:
156
- """
157
- 智能定位输入框并输入文本(需要 AI 密钥)
158
-
159
- Args:
160
- description: 输入框的自然语言描述(如 "用户名输入框")
161
- text: 要输入的文本
162
-
163
- Returns:
164
- {"success": true/false, "message": "..."}
165
-
166
- 示例:
167
- result = await tools.smart_input("邮箱输入框", "test@example.com")
168
-
169
- ⚠️ 如果没有配置 AI 密钥,请使用基础工具:
170
- mobile_input_text_by_id("com.app:id/email_input", "test@example.com")
171
- """
172
- self._ensure_ai_available()
173
-
174
- try:
175
- # 使用智能定位器
176
- result = await self.smart_locator.locate(description)
177
-
178
- if result and result.get('ref'):
179
- ref = result['ref']
180
-
181
- # 根据不同的 ref 类型执行输入
182
- if ':id/' in ref:
183
- # resource-id
184
- element = self.client.u2(resourceId=ref)
185
- element.set_text(text)
186
- return {
187
- "success": True,
188
- "message": f"智能输入成功: {description} = {text}",
189
- "ref": ref
190
- }
191
- else:
192
- return {
193
- "success": False,
194
- "message": f"定位成功但无法输入: {ref}",
195
- "suggestion": "请使用 mobile_find_elements_by_class('android.widget.EditText') 查找输入框"
196
- }
197
- else:
198
- return {
199
- "success": False,
200
- "message": f"智能定位失败: {description}",
201
- "suggestion": "请使用 mobile_list_elements() 查找输入框,然后使用 mobile_input_text_by_id()"
202
- }
203
- except Exception as e:
204
- return {
205
- "success": False,
206
- "message": f"智能输入失败: {str(e)}",
207
- "suggestion": "建议使用基础工具: mobile_input_text_by_id()"
208
- }
209
-
210
- async def analyze_screenshot_with_ai(self, screenshot_path: str, description: str) -> Dict:
211
- """
212
- 使用 AI 分析截图并返回坐标(需要 AI 密钥)
213
-
214
- Args:
215
- screenshot_path: 截图文件路径
216
- description: 要查找的元素描述
217
-
218
- Returns:
219
- {
220
- "success": true/false,
221
- "x": 坐标X(如果成功),
222
- "y": 坐标Y(如果成功),
223
- "confidence": 置信度,
224
- "message": "..."
225
- }
226
-
227
- 示例:
228
- # 先截图
229
- screenshot = mobile_take_screenshot("登录页面")
230
-
231
- # 然后用 AI 分析
232
- result = await tools.analyze_screenshot_with_ai(
233
- screenshot['screenshot_path'],
234
- "登录按钮"
235
- )
236
-
237
- # 根据返回的坐标点击
238
- if result['success']:
239
- mobile_click_at_coords(result['x'], result['y'])
240
-
241
- ⚠️ 需要配置支持视觉识别的 AI(如 GPT-4V、Claude 3、Qwen-VL)
242
- """
243
- self._ensure_ai_available()
244
-
245
- try:
246
- # 尝试使用视觉识别
247
- try:
248
- from ..vision.vision_locator import MobileVisionLocator
249
-
250
- vision_locator = MobileVisionLocator(self.client)
251
- result = await vision_locator.locate_element_by_vision(
252
- description,
253
- screenshot_path=screenshot_path
254
- )
255
-
256
- if result and result.get('found'):
257
- x, y = result['x'], result['y']
258
- confidence = result['confidence']
259
-
260
- return {
261
- "success": True,
262
- "x": x,
263
- "y": y,
264
- "confidence": confidence,
265
- "message": f"✅ AI 视觉识别成功: ({x}, {y}), 置信度 {confidence}%"
266
- }
267
- else:
268
- reason = result.get('reason', '未知原因') if result else '未知原因'
269
- return {
270
- "success": False,
271
- "message": f"❌ AI 视觉识别未找到元素: {reason}",
272
- "suggestion": "请检查截图和元素描述是否准确"
273
- }
274
- except ImportError:
275
- return {
276
- "success": False,
277
- "message": "❌ 视觉识别模块未安装",
278
- "suggestion": "安装:pip install dashscope pillow"
279
- }
280
- except Exception as e:
281
- return {
282
- "success": False,
283
- "message": f"❌ 视觉识别失败: {str(e)}",
284
- "suggestion": "请使用基础工具或检查 AI 配置"
285
- }
286
-
287
- def get_ai_status(self) -> Dict:
288
- """
289
- 获取 AI 功能状态
290
-
291
- Returns:
292
- {
293
- "available": true/false,
294
- "provider": "qwen/openai/...",
295
- "message": "..."
296
- }
297
- """
298
- if self.ai_available:
299
- provider = os.getenv('AI_PROVIDER', 'unknown')
300
- return {
301
- "available": True,
302
- "provider": provider,
303
- "message": f"✅ AI 功能已启用 (Provider: {provider})"
304
- }
305
- else:
306
- return {
307
- "available": False,
308
- "provider": None,
309
- "message": "⚠️ AI 功能未配置,当前仅支持基础工具。如需启用智能定位,请配置 AI 密钥。"
310
- }
311
-
mcp/__init__.py DELETED
@@ -1,8 +0,0 @@
1
- """
2
- Mobile MCP Server Package
3
- """
4
- from .mcp_server import MobileMCPServer
5
-
6
- __all__ = ['MobileMCPServer']
7
-
8
-