#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Mobile MCP Server - 统一入口

纯 MCP 方案，完全依赖 Cursor 视觉能力：
- 不需要 AI 密钥
- 精简工具集（约 28 个核心工具，隐藏 13 个冗余工具但保持向后兼容）
- 按平台动态注册（Toast 仅 Android）
- 支持 Android 和 iOS
- 保留 pytest 脚本生成

使用方式：
    python mcp_server.py
    
配置 Cursor：
    {
        "mcpServers": {
            "mobile": {
                "command": "/path/to/venv/bin/python",
                "args": ["/path/to/mobile_mcp/mcp_server.py"],
                "env": {
                    "MOBILE_PLATFORM": "android"  // 或 "ios"
                }
            }
        }
    }
"""

import asyncio
import base64
import json
import os
import sys
from pathlib import Path
from typing import Optional

# 添加项目根目录到 Python 路径
# 支持两种运行方式：
# 1. 从源码运行：__file__ 在 mcp_tools/ 目录下，往上两级到项目根目录
# 2. 从已安装包运行：包已安装时，mobile_mcp 应该可以直接导入
# 先尝试从已安装的包导入，如果失败则从源码路径导入
try:
    # 尝试导入已安装的包
    import mobile_mcp.core.mobile_client
    import mobile_mcp.core.basic_tools_lite
    # 如果成功，说明包已安装，不需要添加路径
except ImportError:
    # 包未安装或导入失败，从源码运行
    # __file__ 在 mcp_tools/mcp_server.py，往上三级到项目根目录
    project_root = Path(__file__).parent.parent.parent
    if str(project_root) not in sys.path:
        sys.path.insert(0, str(project_root))

# 尝试导入 MCP，处理可能的路径冲突
try:
    from mcp.types import Tool, TextContent, ImageContent
    from mcp.server import Server
    from mcp.server.stdio import stdio_server
except ImportError:
    # 如果本地 mcp 目录冲突，从 site-packages 加载
    import importlib.util
    import site
    
    for site_dir in site.getsitepackages():
        mcp_types_path = Path(site_dir) / 'mcp' / 'types.py'
        if mcp_types_path.exists():
            mcp_pkg_path = Path(site_dir) / 'mcp'
            
            # 加载 mcp.types
            spec = importlib.util.spec_from_file_location("mcp.types", mcp_types_path)
            mcp_types = importlib.util.module_from_spec(spec)
            sys.modules['mcp.types'] = mcp_types
            spec.loader.exec_module(mcp_types)
            
            # 加载 mcp.server
            server_init = mcp_pkg_path / 'server' / '__init__.py'
            spec = importlib.util.spec_from_file_location("mcp.server", server_init)
            mcp_server_mod = importlib.util.module_from_spec(spec)
            sys.modules['mcp.server'] = mcp_server_mod
            spec.loader.exec_module(mcp_server_mod)
            
            # 加载 mcp.server.stdio
            stdio_path = mcp_pkg_path / 'server' / 'stdio.py'
            spec = importlib.util.spec_from_file_location("mcp.server.stdio", stdio_path)
            mcp_stdio = importlib.util.module_from_spec(spec)
            sys.modules['mcp.server.stdio'] = mcp_stdio
            spec.loader.exec_module(mcp_stdio)
            
            Tool = mcp_types.Tool
            TextContent = mcp_types.TextContent
            ImageContent = mcp_types.ImageContent
            Server = mcp_server_mod.Server
            stdio_server = mcp_stdio.stdio_server
            break
    else:
        raise ImportError("Cannot find mcp package")


class MobileMCPServer:
    """Mobile MCP Server - 精简版"""
    
    def __init__(self):
        self.client = None
        self.tools = None
        self._initialized = False
        self._last_error = None
        self._cached_platform = None
        
        # Token 优化配置
        try:
            from mobile_mcp.config import Config
            self._compact_desc = Config.COMPACT_TOOL_DESCRIPTION
        except ImportError:
            self._compact_desc = True
    
    @staticmethod
    def format_response(result) -> str:
        """统一格式化返回值（Token 优化：无缩进）"""
        if isinstance(result, (dict, list)):
            return json.dumps(result, ensure_ascii=False, separators=(',', ':'))
        return str(result)
    
    @staticmethod
    def _read_image_as_base64(filepath: str) -> tuple:
        """读取图片文件并返回 (base64_data, mime_type)"""
        path = Path(filepath)
        if not path.exists():
            return None, None
        suffix = path.suffix.lower()
        mime_map = {'.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png'}
        mime_type = mime_map.get(suffix, 'image/jpeg')
        with open(path, 'rb') as f:
            return base64.standard_b64encode(f.read()).decode('utf-8'), mime_type
    
    async def initialize(self):
        """延迟初始化设备连接"""
        # 如果已成功初始化，检查连接是否仍然有效
        if self._initialized and self.tools is not None:
            # 验证设备连接是否仍然有效
            if self._is_connection_valid():
                return
            else:
                # 连接已失效，重置状态
                print("⚠️ 检测到设备连接已断开，正在重新连接...", file=sys.stderr)
                self._initialized = False
                self.client = None
                self.tools = None
        
        platform = self._detect_platform()
        
        try:
            # 尝试导入，如果失败会抛出 ImportError
            try:
                from mobile_mcp.core.mobile_client import MobileClient
                from mobile_mcp.core.basic_tools_lite import BasicMobileToolsLite
            except ImportError as import_err:
                # 如果导入失败，尝试从源码路径导入
                # 这通常发生在开发模式下，包未安装时
                project_root = Path(__file__).parent.parent.parent
                if str(project_root) not in sys.path:
                    sys.path.insert(0, str(project_root))
                # 再次尝试导入
                from mobile_mcp.core.mobile_client import MobileClient
                from mobile_mcp.core.basic_tools_lite import BasicMobileToolsLite
            
            self.client = MobileClient(platform=platform)
            self.tools = BasicMobileToolsLite(self.client)
            self._initialized = True  # 只在成功时标记
            print(f"📱 已连接到 {platform.upper()} 设备", file=sys.stderr)
        except Exception as e:
            error_msg = str(e)
            print(f"⚠️ 设备连接失败: {error_msg}，下次调用时将重试", file=sys.stderr)
            self.client = None
            self.tools = None
            self._last_error = error_msg  # 保存错误信息
            # 不设置 _initialized = True，下次调用会重试
    
    def _is_connection_valid(self) -> bool:
        """检查设备连接是否仍然有效"""
        try:
            if self.client is None:
                return False
            
            # Android: 检查 u2 连接
            if hasattr(self.client, 'u2') and self.client.u2:
                # 尝试获取设备信息，如果失败说明连接断开
                self.client.u2.info
                return True
            
            # iOS: 检查 wda 连接
            if hasattr(self.client, 'wda') and self.client.wda:
                self.client.wda.status()
                return True
            
            # iOS (通过 _ios_client)
            if hasattr(self.client, '_ios_client') and self.client._ios_client:
                if hasattr(self.client._ios_client, 'wda') and self.client._ios_client.wda:
                    self.client._ios_client.wda.status()
                    return True
            
            return False
        except Exception:
            return False
    
    def _detect_platform(self) -> str:
        """自动检测设备平台（结果缓存，避免重复探测）"""
        if self._cached_platform:
            return self._cached_platform
        
        platform = os.getenv("MOBILE_PLATFORM", "").lower()
        if platform in ["android", "ios"]:
            self._cached_platform = platform
            return platform
        
        # 尝试检测 iOS 设备
        try:
            from mobile_mcp.core.ios_device_manager_wda import IOSDeviceManagerWDA
            ios_manager = IOSDeviceManagerWDA()
            if ios_manager.list_devices():
                self._cached_platform = "ios"
                return "ios"
        except Exception:
            pass
        
        self._cached_platform = "android"
        return "android"
    
    def get_tools(self):
        """注册 MCP 工具（精简版）
        
        优化策略：
        - 只暴露核心工具，隐藏冗余/低频工具
        - 所有描述压缩到一行，减少 token 消耗
        - 按平台动态注册（Toast 仅 Android）
        - 隐藏的工具仍可通过 handle_tool_call() 调用（向后兼容）
        
        隐藏工具（仍可调用）：
        mobile_get_screen_size, mobile_screenshot_with_grid, mobile_click_at_coords,
        mobile_long_press_by_percent, mobile_long_press_at_coords, mobile_input_at_coords,
        mobile_find_close_button, mobile_close_ad, mobile_template_close,
        mobile_template_match, mobile_template_match_and_click, mobile_get_toast,
        mobile_get_operation_history
        """
        tools = []
        platform = self._detect_platform()
        
        # ==================== 元素定位 ====================
        tools.append(Tool(
            name="mobile_list_elements",
            description="【首选】列出页面所有可交互元素(低token~500)。"
                        "返回每个元素的text、resource-id、bounds(含无text的图片按钮)。"
                        "无text元素有hint提示(如'进站必看右侧')。"
                        "点击流程：1.text匹配 2.id匹配 3.SoM编号 4.bounds点击",
            inputSchema={"type": "object", "properties": {}, "required": []}
        ))
        
        tools.append(Tool(
            name="mobile_find_nearby",
            description="查找指定文字附近的clickable元素。当目标是图片按钮(无text)时使用。"
                        "例如：find_nearby('进站必看', 'right') 返回'进站必看'右边的clickable元素bounds。",
            inputSchema={
                "type": "object",
                "properties": {
                    "text": {"type": "string", "description": "参照元素的文字"},
                    "direction": {"type": "string", "enum": ["left", "right", "above", "below"], "description": "方向", "default": "right"}
                },
                "required": ["text"]
            }
        ))
        
        tools.append(Tool(
            name="mobile_take_screenshot",
            description="截图查看屏幕(高token~2000)。仅在需要视觉布局或元素无text/id时使用。"
                        "compress=false获取原图(用于template_add添加模板)。确认状态优先用list_elements。",
            inputSchema={
                "type": "object",
                "properties": {
                    "description": {"type": "string", "description": "截图描述"},
                    "compress": {"type": "boolean", "description": "是否压缩", "default": True},
                    "crop_x": {"type": "integer", "description": "裁剪中心X"},
                    "crop_y": {"type": "integer", "description": "裁剪中心Y"},
                    "crop_size": {"type": "integer", "description": "裁剪大小"}
                },
                "required": []
            }
        ))
        
        tools.append(Tool(
            name="mobile_screenshot_with_som",
            description="SoM标注截图：红色框=有text元素，蓝色框=无text图片按钮。"
                        "所有编号都可用click_by_som点击。适合图片按钮定位。",
            inputSchema={"type": "object", "properties": {}, "required": []}
        ))
        
        tools.append(Tool(
            name="mobile_click_by_som",
            description="根据SoM编号点击，配合screenshot_with_som使用。",
            inputSchema={
                "type": "object",
                "properties": {
                    "index": {"type": "integer", "description": "元素编号(从1开始)"}
                },
                "required": ["index"]
            }
        ))
        
        # ==================== 点击操作 ====================
        tools.append(Tool(
            name="mobile_click_by_text",
            description="通过文本点击元素(最推荐，精确匹配)。"
                        "verify：点击后自动验证该文本出现，省去额外list_elements调用。"
                        "position：多个相同文案时指定top/bottom/left/right/center。",
            inputSchema={
                "type": "object",
                "properties": {
                    "text": {"type": "string", "description": "元素文本"},
                    "position": {"type": "string", "description": "位置：top/bottom/left/right/center"},
                    "verify": {"type": "string", "description": "点击后验证文本"}
                },
                "required": ["text"]
            }
        ))
        
        tools.append(Tool(
            name="mobile_click_by_id",
            description="通过resource-id点击，index指定第几个(从0开始)。",
            inputSchema={
                "type": "object",
                "properties": {
                    "resource_id": {"type": "string", "description": "resource-id"},
                    "index": {"type": "integer", "description": "第几个(从0开始)", "default": 0}
                },
                "required": ["resource_id"]
            }
        ))
        
        tools.append(Tool(
            name="mobile_click_by_percent",
            description="百分比坐标点击(最后手段)。(0,0)=左上角，(50,50)=屏幕中心。优先用text/id/som/bounds。",
            inputSchema={
                "type": "object",
                "properties": {
                    "x_percent": {"type": "number", "description": "X百分比(0-100)"},
                    "y_percent": {"type": "number", "description": "Y百分比(0-100)"}
                },
                "required": ["x_percent", "y_percent"]
            }
        ))
        
        tools.append(Tool(
            name="mobile_click_by_bounds",
            description="根据bounds区域点击中心点。适用于无text的图片按钮，从list_elements获取bounds后使用。"
                        "点击优先级: 1.text 2.id 3.som 4.bounds 5.percent",
            inputSchema={
                "type": "object",
                "properties": {
                    "x1": {"type": "integer", "description": "左上角X"},
                    "y1": {"type": "integer", "description": "左上角Y"},
                    "x2": {"type": "integer", "description": "右下角X"},
                    "y2": {"type": "integer", "description": "右下角Y"},
                    "bounds_str": {"type": "string", "description": "或直接传bounds字符串如[360,775][693,852]"}
                },
                "required": []
            }
        ))
        
        # ==================== 长按操作 ====================
        tools.append(Tool(
            name="mobile_long_press_by_text",
            description="通过文本长按，duration指定秒数(默认1.0)。",
            inputSchema={
                "type": "object",
                "properties": {
                    "text": {"type": "string", "description": "文本内容"},
                    "duration": {"type": "number", "description": "长按秒数,默认1.0"}
                },
                "required": ["text"]
            }
        ))
        
        tools.append(Tool(
            name="mobile_long_press_by_id",
            description="通过resource-id长按，duration指定秒数(默认1.0)。",
            inputSchema={
                "type": "object",
                "properties": {
                    "resource_id": {"type": "string", "description": "resource-id"},
                    "duration": {"type": "number", "description": "长按秒数,默认1.0"}
                },
                "required": ["resource_id"]
            }
        ))
        
        # ==================== 输入操作 ====================
        tools.append(Tool(
            name="mobile_input_text_by_id",
            description="通过resource-id输入文本(会替换输入框已有内容)。",
            inputSchema={
                "type": "object",
                "properties": {
                    "resource_id": {"type": "string", "description": "resource-id"},
                    "text": {"type": "string", "description": "输入文本"}
                },
                "required": ["resource_id", "text"]
            }
        ))
        
        # ==================== 导航操作 ====================
        tools.append(Tool(
            name="mobile_swipe",
            description="滑动屏幕。up=页面上滚(看下方内容)，down=页面下滚(看上方内容)。"
                        "左右滑动可指定y_percent(高度位置)和distance_percent(滑动幅度)。",
            inputSchema={
                "type": "object",
                "properties": {
                    "direction": {"type": "string", "enum": ["up", "down", "left", "right"], "description": "方向"},
                    "y": {"type": "integer", "description": "左右滑动高度坐标(px)"},
                    "y_percent": {"type": "number", "description": "左右滑动高度百分比(0-100)"},
                    "distance": {"type": "integer", "description": "左右滑动距离(px)"},
                    "distance_percent": {"type": "number", "description": "左右滑动距离百分比(0-100)"}
                },
                "required": ["direction"]
            }
        ))
        
        tools.append(Tool(
            name="mobile_drag_progress_bar",
            description="智能拖动进度条，自动检测位置。direction:left(倒退)/right(前进)，distance_percent控制幅度。",
            inputSchema={
                "type": "object",
                "properties": {
                    "direction": {"type": "string", "enum": ["left", "right"], "description": "left倒退/right前进"},
                    "distance_percent": {"type": "number", "description": "拖动距离百分比(0-100),默认30"},
                    "y_percent": {"type": "number", "description": "进度条位置百分比(可选,自动检测)"},
                    "y": {"type": "integer", "description": "进度条位置坐标(可选,自动检测)"}
                },
                "required": []
            }
        ))
        
        tools.append(Tool(
            name="mobile_press_key",
            description="按物理键。支持：home(主页)、back(返回)、enter(确认)、search(搜索)、power(电源)。",
            inputSchema={
                "type": "object",
                "properties": {
                    "key": {"type": "string", "description": "按键名"}
                },
                "required": ["key"]
            }
        ))
        
        tools.append(Tool(
            name="mobile_wait",
            description="等待指定秒数。",
            inputSchema={
                "type": "object",
                "properties": {
                    "seconds": {"type": "number", "description": "等待秒数"}
                },
                "required": ["seconds"]
            }
        ))
        
        tools.append(Tool(
            name="mobile_hide_keyboard",
            description="收起软键盘。任何输入操作后、需要点击被键盘遮挡的元素前，都应调用。",
            inputSchema={"type": "object", "properties": {}, "required": []}
        ))
        
        # ==================== 应用管理 ====================
        tools.append(Tool(
            name="mobile_launch_app",
            description="启动应用(需包名)。",
            inputSchema={
                "type": "object",
                "properties": {
                    "package_name": {"type": "string", "description": "包名"}
                },
                "required": ["package_name"]
            }
        ))
        
        tools.append(Tool(
            name="mobile_terminate_app",
            description="终止应用(需包名)。",
            inputSchema={
                "type": "object",
                "properties": {
                    "package_name": {"type": "string", "description": "包名"}
                },
                "required": ["package_name"]
            }
        ))
        
        tools.append(Tool(
            name="mobile_list_apps",
            description="列出已安装应用，可按关键词过滤。",
            inputSchema={
                "type": "object",
                "properties": {
                    "filter": {"type": "string", "description": "过滤词"}
                },
                "required": []
            }
        ))
        
        # ==================== 设备管理 ====================
        tools.append(Tool(
            name="mobile_list_devices",
            description="列出已连接设备。",
            inputSchema={"type": "object", "properties": {}, "required": []}
        ))
        
        tools.append(Tool(
            name="mobile_check_connection",
            description="检查设备连接状态。",
            inputSchema={"type": "object", "properties": {}, "required": []}
        ))
        
        # ==================== 弹窗处理 ====================
        tools.append(Tool(
            name="mobile_close_popup",
            description="智能检测并关闭弹窗(含广告弹窗)。"
                        "自动查找x/关闭/跳过按钮，无弹窗时安全返回。启动App后建议调用。",
            inputSchema={
                "type": "object",
                "properties": {
                    "popup_detected": {"type": "boolean", "description": "已确认有弹窗时传true跳过检测"},
                    "popup_bounds": {"type": "array", "items": {"type": "number"}, "description": "弹窗边界[x1,y1,x2,y2]"}
                },
                "required": []
            }
        ))
        
        tools.append(Tool(
            name="mobile_assert_text",
            description="断言页面包含指定文本。",
            inputSchema={
                "type": "object",
                "properties": {
                    "text": {"type": "string", "description": "期望文本"}
                },
                "required": ["text"]
            }
        ))
        
        # ==================== Toast 检测（仅 Android）====================
        if platform == "android":
            tools.append(Tool(
                name="mobile_start_toast_watch",
                description="开始监听Toast(仅Android)，必须在触发操作前调用。",
                inputSchema={"type": "object", "properties": {}, "required": []}
            ))
            
            tools.append(Tool(
                name="mobile_assert_toast",
                description="断言Toast内容(仅Android)，配合start_toast_watch使用。",
                inputSchema={
                    "type": "object",
                    "properties": {
                        "expected_text": {"type": "string", "description": "期望文本"},
                        "timeout": {"type": "number", "description": "超时秒数"},
                        "contains": {"type": "boolean", "description": "包含匹配(默认true)"}
                    },
                    "required": ["expected_text"]
                }
            ))
        
        # ==================== pytest 脚本生成 ====================
        tools.append(Tool(
            name="mobile_clear_operation_history",
            description="清空操作历史，录制测试脚本前调用。",
            inputSchema={"type": "object", "properties": {}, "required": []}
        ))
        
        tools.append(Tool(
            name="mobile_generate_test_script",
            description="基于操作历史生成pytest脚本。",
            inputSchema={
                "type": "object",
                "properties": {
                    "test_name": {"type": "string", "description": "用例名"},
                    "package_name": {"type": "string", "description": "包名"},
                    "filename": {"type": "string", "description": "文件名(不含.py)"}
                },
                "required": ["test_name", "package_name", "filename"]
            }
        ))
        
        # ==================== 模板管理 ====================
        tools.append(Tool(
            name="mobile_template_add",
            description="添加图像模板(X号/复选框等)用于模板匹配关闭弹窗。",
            inputSchema={
                "type": "object",
                "properties": {
                    "template_name": {"type": "string", "description": "模板名"},
                    "category": {"type": "string", "description": "分类(close_buttons/checkbox等)", "default": "close_buttons"},
                    "x_percent": {"type": "number", "description": "X百分比"},
                    "y_percent": {"type": "number", "description": "Y百分比"},
                    "size": {"type": "integer", "description": "裁剪大小(px)"},
                    "screenshot_path": {"type": "string", "description": "截图路径"},
                    "x": {"type": "integer", "description": "左上X"},
                    "y": {"type": "integer", "description": "左上Y"},
                    "width": {"type": "integer", "description": "宽"},
                    "height": {"type": "integer", "description": "高"}
                },
                "required": ["template_name"]
            }
        ))
        
        # ==================== 设备增强 ====================
        tools.append(Tool(
            name="mobile_get_current_app",
            description="获取当前前台应用的包名和Activity。",
            inputSchema={"type": "object", "properties": {}, "required": []}
        ))
        
        tools.append(Tool(
            name="mobile_open_deep_link",
            description="通过深度链接/URL直接跳转页面，跳过多步导航。",
            inputSchema={
                "type": "object",
                "properties": {
                    "url": {"type": "string", "description": "深度链接URL(如 myapp://page 或 https://...)"}
                },
                "required": ["url"]
            }
        ))
        
        tools.append(Tool(
            name="mobile_get_clipboard",
            description="获取设备剪贴板内容，用于验证复制功能。",
            inputSchema={"type": "object", "properties": {}, "required": []}
        ))
        
        tools.append(Tool(
            name="mobile_set_clipboard",
            description="设置设备剪贴板内容，用于粘贴测试数据。",
            inputSchema={
                "type": "object",
                "properties": {
                    "text": {"type": "string", "description": "要设置的文本"}
                },
                "required": ["text"]
            }
        ))
        
        # ==================== 录屏功能（仅 Android）====================
        if platform == "android":
            tools.append(Tool(
                name="mobile_start_screen_record",
                description="开始录屏(仅Android)。录制操作过程视频，用于回放/分析/存证。调用stop结束。",
                inputSchema={"type": "object", "properties": {}, "required": []}
            ))
            
            tools.append(Tool(
                name="mobile_stop_screen_record",
                description="停止录屏并保存视频(仅Android)。返回本地视频文件路径。需先调用start_screen_record。",
                inputSchema={"type": "object", "properties": {}, "required": []}
            ))
        
        # ==================== Cursor 会话管理 ====================
        tools.append(Tool(
            name="mobile_open_new_chat",
            description="打开Cursor新会话，用于飞书用例批量执行分批继续。",
            inputSchema={
                "type": "object",
                "properties": {
                    "message": {"type": "string", "description": "发送消息", "default": "继续执行飞书用例"}
                },
                "required": []
            }
        ))
        
        return tools
    
    async def handle_tool_call(self, name: str, arguments: dict):
        """处理工具调用"""
        await self.initialize()
        
        if not self.tools:
            # 提供详细的错误信息和解决方案
            error_detail = self._last_error or "未知错误"
            help_msg = (
                f"❌ 设备连接失败\n\n"
                f"错误详情: {error_detail}\n\n"
                f"🔧 解决方案:\n"
                f"1. 检查 USB 连接: adb devices\n"
                f"2. 重启 adb: adb kill-server && adb start-server\n"
                f"3. 初始化 uiautomator2: python -m uiautomator2 init\n"
                f"4. 手机上允许 USB 调试授权\n"
                f"5. 确保手机已解锁\n\n"
                f"完成后请重试操作。"
            )
            return [TextContent(type="text", text=help_msg)]
        
        try:
            # 截图
            if name == "mobile_take_screenshot":
                result = self.tools.take_screenshot(
                    description=arguments.get("description", ""),
                    compress=arguments.get("compress", True),
                    crop_x=arguments.get("crop_x", 0),
                    crop_y=arguments.get("crop_y", 0),
                    crop_size=arguments.get("crop_size", 0)
                )
                return [TextContent(type="text", text=self.format_response(result))]
            
            elif name == "mobile_get_screen_size":
                result = self.tools.get_screen_size()
                return [TextContent(type="text", text=self.format_response(result))]
            
            elif name == "mobile_screenshot_with_grid":
                result = self.tools.take_screenshot_with_grid(
                    grid_size=arguments.get("grid_size", 100),
                    show_popup_hints=arguments.get("show_popup_hints", False)
                )
                return [TextContent(type="text", text=self.format_response(result))]
            
            elif name == "mobile_screenshot_with_som":
                result = self.tools.take_screenshot_with_som()
                return [TextContent(type="text", text=self.format_response(result))]
            
            elif name == "mobile_click_by_som":
                result = self.tools.click_by_som(arguments["index"])
                return [TextContent(type="text", text=self.format_response(result))]
            
            # 点击
            elif name == "mobile_click_at_coords":
                result = self.tools.click_at_coords(
                    arguments["x"], 
                    arguments["y"],
                    arguments.get("image_width", 0),
                    arguments.get("image_height", 0),
                    arguments.get("crop_offset_x", 0),
                    arguments.get("crop_offset_y", 0),
                    arguments.get("original_img_width", 0),
                    arguments.get("original_img_height", 0)
                )
                return [TextContent(type="text", text=self.format_response(result))]
            
            elif name == "mobile_click_by_text":
                result = self.tools.click_by_text(
                    arguments["text"],
                    position=arguments.get("position"),
                    verify=arguments.get("verify")
                )
                return [TextContent(type="text", text=self.format_response(result))]
            
            elif name == "mobile_click_by_id":
                result = self.tools.click_by_id(
                    arguments["resource_id"],
                    arguments.get("index", 0)
                )
                return [TextContent(type="text", text=self.format_response(result))]
            
            elif name == "mobile_click_by_percent":
                result = self.tools.click_by_percent(arguments["x_percent"], arguments["y_percent"])
                return [TextContent(type="text", text=self.format_response(result))]
            
            elif name == "mobile_click_by_bounds":
                # 支持两种方式：直接传x1,y1,x2,y2 或 传bounds_str
                if "bounds_str" in arguments and arguments["bounds_str"]:
                    result = self.tools.click_by_bounds_str(arguments["bounds_str"])
                else:
                    result = self.tools.click_by_bounds(
                        arguments.get("x1"), arguments.get("y1"),
                        arguments.get("x2"), arguments.get("y2")
                    )
                return [TextContent(type="text", text=self.format_response(result))]
            
            # 长按
            elif name == "mobile_long_press_by_id":
                result = self.tools.long_press_by_id(
                    arguments["resource_id"],
                    arguments.get("duration", 1.0)
                )
                return [TextContent(type="text", text=self.format_response(result))]
            
            elif name == "mobile_long_press_by_text":
                result = self.tools.long_press_by_text(
                    arguments["text"],
                    arguments.get("duration", 1.0)
                )
                return [TextContent(type="text", text=self.format_response(result))]
            
            elif name == "mobile_long_press_by_percent":
                result = self.tools.long_press_by_percent(
                    arguments["x_percent"],
                    arguments["y_percent"],
                    arguments.get("duration", 1.0)
                )
                return [TextContent(type="text", text=self.format_response(result))]
            
            elif name == "mobile_long_press_at_coords":
                result = self.tools.long_press_at_coords(
                    arguments["x"],
                    arguments["y"],
                    arguments.get("duration", 1.0),
                    arguments.get("image_width", 0),
                    arguments.get("image_height", 0),
                    arguments.get("crop_offset_x", 0),
                    arguments.get("crop_offset_y", 0),
                    arguments.get("original_img_width", 0),
                    arguments.get("original_img_height", 0)
                )
                return [TextContent(type="text", text=self.format_response(result))]
            
            # 输入
            elif name == "mobile_input_text_by_id":
                result = self.tools.input_text_by_id(arguments["resource_id"], arguments["text"])
                return [TextContent(type="text", text=self.format_response(result))]
            
            elif name == "mobile_input_at_coords":
                result = self.tools.input_at_coords(arguments["x"], arguments["y"], arguments["text"])
                return [TextContent(type="text", text=self.format_response(result))]
            
            # 导航
            elif name == "mobile_swipe":
                result = await self.tools.swipe(
                    arguments["direction"],
                    y=arguments.get("y"),
                    y_percent=arguments.get("y_percent"),
                    distance=arguments.get("distance"),
                    distance_percent=arguments.get("distance_percent")
                )
                return [TextContent(type="text", text=self.format_response(result))]
            
            elif name == "mobile_drag_progress_bar":
                result = await self.tools.drag_progress_bar(
                    direction=arguments.get("direction", "right"),
                    distance_percent=arguments.get("distance_percent", 30.0),
                    y_percent=arguments.get("y_percent"),
                    y=arguments.get("y")
                )
                return [TextContent(type="text", text=self.format_response(result))]
            
            elif name == "mobile_press_key":
                result = await self.tools.press_key(arguments["key"])
                return [TextContent(type="text", text=self.format_response(result))]
            
            elif name == "mobile_wait":
                result = self.tools.wait(arguments["seconds"])
                return [TextContent(type="text", text=self.format_response(result))]
            
            elif name == "mobile_hide_keyboard":
                result = await self.tools.hide_keyboard()
                return [TextContent(type="text", text=self.format_response(result))]
            
            # 应用管理
            elif name == "mobile_launch_app":
                result = await self.tools.launch_app(arguments["package_name"])
                return [TextContent(type="text", text=self.format_response(result))]
            
            elif name == "mobile_terminate_app":
                result = self.tools.terminate_app(arguments["package_name"])
                return [TextContent(type="text", text=self.format_response(result))]
            
            elif name == "mobile_list_apps":
                result = self.tools.list_apps(arguments.get("filter", ""))
                return [TextContent(type="text", text=self.format_response(result))]
            
            # 设备管理
            elif name == "mobile_list_devices":
                result = self.tools.list_devices()
                return [TextContent(type="text", text=self.format_response(result))]
            
            elif name == "mobile_check_connection":
                result = self.tools.check_connection()
                return [TextContent(type="text", text=self.format_response(result))]
            
            # 辅助
            elif name == "mobile_list_elements":
                result = self.tools.list_elements()
                return [TextContent(type="text", text=self.format_response(result))]
            
            elif name == "mobile_find_nearby":
                text = arguments.get("text", "")
                direction = arguments.get("direction", "right")
                result = self.tools.find_nearby(text, direction)
                return [TextContent(type="text", text=self.format_response(result))]
            
            elif name == "mobile_find_close_button":
                result = self.tools.find_close_button()
                return [TextContent(type="text", text=self.format_response(result))]
            
            elif name == "mobile_close_popup":
                popup_detected = arguments.get("popup_detected")
                popup_bounds = arguments.get("popup_bounds")
                # 如果传入了popup_bounds，转换为tuple
                if popup_bounds and isinstance(popup_bounds, list) and len(popup_bounds) == 4:
                    popup_bounds = tuple(popup_bounds)
                elif popup_bounds:
                    popup_bounds = None  # 格式不正确，忽略
                result = self.tools.close_popup(
                    popup_detected=popup_detected,
                    popup_bounds=popup_bounds
                )
                return [TextContent(type="text", text=self.format_response(result))]
            
            elif name == "mobile_assert_text":
                result = self.tools.assert_text(arguments["text"])
                return [TextContent(type="text", text=self.format_response(result))]
            
            # Toast 检测（仅 Android）
            elif name == "mobile_start_toast_watch":
                result = self.tools.start_toast_watch()
                return [TextContent(type="text", text=self.format_response(result))]
            
            elif name == "mobile_get_toast":
                timeout = arguments.get("timeout", 5.0)
                reset_first = arguments.get("reset_first", False)
                result = self.tools.get_toast(timeout=timeout, reset_first=reset_first)
                return [TextContent(type="text", text=self.format_response(result))]
            
            elif name == "mobile_assert_toast":
                result = self.tools.assert_toast(
                    expected_text=arguments["expected_text"],
                    timeout=arguments.get("timeout", 5.0),
                    contains=arguments.get("contains", True)
                )
                return [TextContent(type="text", text=self.format_response(result))]
            
            # 脚本生成
            elif name == "mobile_get_operation_history":
                result = self.tools.get_operation_history(arguments.get("limit"))
                return [TextContent(type="text", text=self.format_response(result))]
            
            elif name == "mobile_clear_operation_history":
                result = self.tools.clear_operation_history()
                return [TextContent(type="text", text=self.format_response(result))]
            
            elif name == "mobile_generate_test_script":
                result = self.tools.generate_test_script(
                    arguments["test_name"],
                    arguments["package_name"],
                    arguments["filename"]
                )
                return [TextContent(type="text", text=self.format_response(result))]
            
            # 智能关闭广告弹窗
            elif name == "mobile_close_ad":
                result = self.tools.close_ad_popup(auto_learn=True)
                return [TextContent(type="text", text=self.format_response(result))]
            
            # 模板匹配（精简版）
            elif name == "mobile_template_close":
                click = arguments.get("click", True)
                threshold = arguments.get("threshold", 0.75)
                if click:
                    result = self.tools.template_click_close(threshold=threshold)
                else:
                    result = self.tools.template_match_close(threshold=threshold)
                return [TextContent(type="text", text=self.format_response(result))]
            
            elif name == "mobile_template_add":
                template_name = arguments["template_name"]
                category = arguments.get("category", "close_buttons")
                # 判断使用哪种方式
                if "x_percent" in arguments and "y_percent" in arguments:
                    # 百分比方式
                    result = self.tools.template_add_by_percent(
                        arguments["x_percent"],
                        arguments["y_percent"],
                        arguments.get("size", 80),
                        template_name,
                        category=category
                    )
                elif "screenshot_path" in arguments:
                    # 像素方式
                    result = self.tools.template_add(
                        arguments["screenshot_path"],
                        arguments["x"],
                        arguments["y"],
                        arguments["width"],
                        arguments["height"],
                        template_name,
                        category=category
                    )
                else:
                    result = {"success": False, "error": "请提供 x_percent/y_percent 或 screenshot_path/x/y/width/height"}
                return [TextContent(type="text", text=self.format_response(result))]

            elif name == "mobile_template_match":
                result = self.tools.template_match(
                    template_name=arguments.get("template_name"),
                    category=arguments.get("category"),
                    threshold=arguments.get("threshold", 0.75)
                )
                return [TextContent(type="text", text=self.format_response(result))]

            elif name == "mobile_template_match_and_click":
                result = self.tools.template_match_and_click(
                    template_name=arguments.get("template_name"),
                    category=arguments.get("category"),
                    threshold=arguments.get("threshold", 0.75)
                )
                return [TextContent(type="text", text=self.format_response(result))]
            
            # 设备增强
            elif name == "mobile_get_current_app":
                result = self.tools.get_current_app()
                return [TextContent(type="text", text=self.format_response(result))]
            
            elif name == "mobile_open_deep_link":
                result = self.tools.open_deep_link(arguments["url"])
                return [TextContent(type="text", text=self.format_response(result))]
            
            elif name == "mobile_get_clipboard":
                result = self.tools.get_clipboard()
                return [TextContent(type="text", text=self.format_response(result))]
            
            elif name == "mobile_set_clipboard":
                result = self.tools.set_clipboard(arguments["text"])
                return [TextContent(type="text", text=self.format_response(result))]
            
            # 录屏
            elif name == "mobile_start_screen_record":
                result = self.tools.start_screen_record()
                return [TextContent(type="text", text=self.format_response(result))]
            
            elif name == "mobile_stop_screen_record":
                result = self.tools.stop_screen_record()
                return [TextContent(type="text", text=self.format_response(result))]
            
            # Cursor 会话管理
            elif name == "mobile_open_new_chat":
                message = arguments.get("message", "继续执行飞书用例")
                result = self.tools.open_new_chat(message)
                return [TextContent(type="text", text=self.format_response(result))]
            
            else:
                return [TextContent(type="text", text=f"❌ 未知工具: {name}")]
        
        except Exception as e:
            import traceback
            error_msg = f"❌ 执行失败: {str(e)}\n{traceback.format_exc()}"
            return [TextContent(type="text", text=error_msg)]


async def async_main():
    """启动 MCP Server（异步版本）"""
    server = MobileMCPServer()
    mcp_server = Server("mobile-mcp")
    
    @mcp_server.list_tools()
    async def list_tools():
        return server.get_tools()
    
    @mcp_server.call_tool()
    async def call_tool(name: str, arguments: dict):
        return await server.handle_tool_call(name, arguments)
    
    tool_count = len(server.get_tools())
    print(f"🚀 Mobile MCP Server 启动中... [{tool_count} 个工具]", file=sys.stderr)
    print("📱 支持 Android / iOS", file=sys.stderr)
    print("👁️ 完全依赖 Cursor 视觉能力，无需 AI 密钥", file=sys.stderr)
    
    async with stdio_server() as (read_stream, write_stream):
        await mcp_server.run(read_stream, write_stream, mcp_server.create_initialization_options())


def main():
    """入口点函数（供 pip 安装后使用）"""
    asyncio.run(async_main())


if __name__ == "__main__":
    main()

