Files
httprunner/uixt/mcp_server.md
lilong.129 f702a3cc78 docs: add comprehensive documentation for MCP server
- Add detailed package-level documentation for mcp_server.go
- Create MCP_SERVER_DOCUMENTATION.md with complete implementation guide
- Create MCP_TOOLS_REFERENCE.md with quick reference for all tools
- Add extensive code comments for key structures and functions
- Document architecture, features, extension guide, and best practices
- Include usage examples and troubleshooting information

This provides complete documentation for developers to understand,
use, and extend the HttpRunner MCP server functionality.
2025-05-30 00:37:26 +08:00

19 KiB

HttpRunner MCP Server 完整说明文档

📖 概述

HttpRunner MCP Server 是基于 Model Context Protocol (MCP) 协议实现的 UI 自动化测试服务器,它将 HttpRunner 的强大 UI 自动化能力通过标准化的 MCP 接口暴露给 AI 模型和其他客户端。

🎯 核心功能特性

1. 设备管理

  • 设备发现: 自动发现 Android/iOS 设备和模拟器
  • 设备选择: 支持通过序列号/UDID 选择特定设备
  • 多平台支持: Android、iOS、Harmony、Browser 全平台覆盖

2. 交互操作

  • 点击操作: 支持坐标点击、OCR 文本点击、CV 图像识别点击
  • 滑动操作: 方向滑动、坐标滑动、智能滑动查找
  • 拖拽操作: 精确的拖拽控制,支持反作弊
  • 输入操作: 文本输入、按键操作

3. 应用管理

  • 应用控制: 启动、终止、安装、卸载、清除数据
  • 包名查询: 获取设备上所有应用包名
  • 前台应用: 获取当前前台应用信息

4. 屏幕操作

  • 截图功能: 高质量屏幕截图,支持 Base64 编码
  • 屏幕信息: 获取屏幕尺寸、方向等信息
  • UI 层次: 获取界面元素层次结构

5. 高级功能

  • AI 驱动: 支持 AI 模型驱动的智能操作
  • 反作弊机制: 内置反作弊检测和规避
  • Web 自动化: 支持浏览器自动化操作
  • 时间控制: 精确的等待和延时控制

🏗️ 架构设计

整体架构

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   MCP Client    │    │   MCP Server    │    │  XTDriver Core  │
│   (AI Model)    │◄──►│  (mcp_server)   │◄──►│   (UI Engine)   │
└─────────────────┘    └─────────────────┘    └─────────────────┘
                              │
                              ▼
                       ┌─────────────────┐
                       │  Device Layer   │
                       │ Android/iOS/Web │
                       └─────────────────┘

核心组件

1. MCPServer4XTDriver

type MCPServer4XTDriver struct {
    mcpServer     *server.MCPServer                // MCP 协议服务器
    mcpTools      []mcp.Tool                       // 注册的工具列表
    actionToolMap map[option.ActionName]ActionTool // 动作到工具的映射
}

2. ActionTool 接口

type ActionTool interface {
    Name() option.ActionName                                              // 工具名称
    Description() string                                                  // 工具描述
    Options() []mcp.ToolOption                                           // MCP 选项定义
    Implement() server.ToolHandlerFunc                                   // 工具实现逻辑
    ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) // 动作转换
}

🛠️ 实现思路

1. 纯 ActionTool 架构

采用纯 ActionTool 风格架构,每个 MCP 工具都是独立的结构体:

type ToolTapXY struct{}

func (t *ToolTapXY) Name() option.ActionName {
    return option.ACTION_TapXY
}

func (t *ToolTapXY) Implement() server.ToolHandlerFunc {
    return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
        // 1. 设置驱动器
        driverExt, err := setupXTDriver(ctx, request.Params.Arguments)

        // 2. 解析参数
        unifiedReq, err := parseActionOptions(request.Params.Arguments)

        // 3. 执行操作
        err = driverExt.TapXY(unifiedReq.X, unifiedReq.Y, opts...)

        // 4. 返回结果
        return mcp.NewToolResultText("操作成功"), nil
    }
}

2. 统一参数处理

使用 parseActionOptions 函数统一处理 MCP 请求参数:

func parseActionOptions(arguments map[string]any) (*option.ActionOptions, error) {
    b, err := json.Marshal(arguments)
    if err != nil {
        return nil, fmt.Errorf("marshal arguments failed: %w", err)
    }

    var actionOptions option.ActionOptions
    if err := json.Unmarshal(b, &actionOptions); err != nil {
        return nil, fmt.Errorf("unmarshal to ActionOptions failed: %w", err)
    }

    return &actionOptions, nil
}

3. 设备管理策略

通过 setupXTDriver 函数实现设备的统一管理:

func setupXTDriver(ctx context.Context, arguments map[string]any) (*XTDriver, error) {
    // 1. 解析设备参数
    platform := arguments["platform"].(string)
    serial := arguments["serial"].(string)

    // 2. 获取或创建驱动器
    driverExt, err := GetOrCreateXTDriver(
        option.WithPlatform(platform),
        option.WithSerial(serial),
    )

    return driverExt, err
}

4. 错误处理机制

统一的错误处理和日志记录:

if err != nil {
    log.Error().Err(err).Str("tool", toolName).Msg("tool execution failed")
    return mcp.NewToolResultError(fmt.Sprintf("操作失败: %s", err.Error())), nil
}

🔧 如何扩展接入新工具

步骤 1: 定义工具结构体

// 新工具:长按操作
type ToolLongPress struct{}

func (t *ToolLongPress) Name() option.ActionName {
    return option.ACTION_LongPress // 需要在 option 包中定义
}

func (t *ToolLongPress) Description() string {
    return "在指定坐标执行长按操作"
}

步骤 2: 定义 MCP 选项

func (t *ToolLongPress) Options() []mcp.ToolOption {
    return []mcp.ToolOption{
        mcp.WithString("platform", mcp.Enum("android", "ios"), mcp.Description("设备平台")),
        mcp.WithString("serial", mcp.Description("设备序列号")),
        mcp.WithNumber("x", mcp.Description("X 坐标")),
        mcp.WithNumber("y", mcp.Description("Y 坐标")),
        mcp.WithNumber("duration", mcp.Description("长按持续时间(秒)")),
        mcp.WithBoolean("anti_risk", mcp.Description("是否启用反作弊")),
    }
}

步骤 3: 实现工具逻辑

func (t *ToolLongPress) Implement() server.ToolHandlerFunc {
    return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
        // 1. 设置驱动器
        driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
        if err != nil {
            return nil, fmt.Errorf("setup driver failed: %w", err)
        }

        // 2. 解析参数
        unifiedReq, err := parseActionOptions(request.Params.Arguments)
        if err != nil {
            return nil, err
        }

        // 3. 参数验证
        if unifiedReq.X == 0 || unifiedReq.Y == 0 {
            return nil, fmt.Errorf("x and y coordinates are required")
        }

        // 4. 构建选项
        opts := []option.ActionOption{}
        if unifiedReq.Duration > 0 {
            opts = append(opts, option.WithDuration(unifiedReq.Duration))
        }
        if unifiedReq.AntiRisk {
            opts = append(opts, option.WithAntiRisk(true))
        }

        // 5. 执行操作
        log.Info().Float64("x", unifiedReq.X).Float64("y", unifiedReq.Y).
            Float64("duration", unifiedReq.Duration).Msg("executing long press")

        err = driverExt.LongPress(unifiedReq.X, unifiedReq.Y, opts...)
        if err != nil {
            return mcp.NewToolResultError(fmt.Sprintf("长按操作失败: %s", err.Error())), nil
        }

        // 6. 返回结果
        return mcp.NewToolResultText(fmt.Sprintf("成功在坐标 (%.2f, %.2f) 执行长按操作",
            unifiedReq.X, unifiedReq.Y)), nil
    }
}

步骤 4: 实现动作转换

func (t *ToolLongPress) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
    if params, err := builtin.ConvertToFloat64Slice(action.Params); err == nil && len(params) >= 2 {
        arguments := map[string]any{
            "x": params[0],
            "y": params[1],
        }

        // 添加持续时间
        if len(params) > 2 {
            arguments["duration"] = params[2]
        }

        // 提取动作选项
        extractActionOptionsToArguments(action.GetOptions(), arguments)

        return buildMCPCallToolRequest(t.Name(), arguments), nil
    }
    return mcp.CallToolRequest{}, fmt.Errorf("invalid long press params: %v", action.Params)
}

步骤 5: 注册工具

registerTools() 方法中添加新工具:

func (s *MCPServer4XTDriver) registerTools() {
    // ... 现有工具注册 ...

    // 注册新工具
    s.registerTool(&ToolLongPress{})

    // ... 其他工具 ...
}

步骤 6: 添加单元测试

func TestToolLongPress(t *testing.T) {
    tool := &ToolLongPress{}

    // 测试工具基本信息
    assert.Equal(t, option.ACTION_LongPress, tool.Name())
    assert.Contains(t, tool.Description(), "长按")

    // 测试选项定义
    options := tool.Options()
    assert.NotEmpty(t, options)

    // 测试动作转换
    action := MobileAction{
        Method: option.ACTION_LongPress,
        Params: []float64{100, 200, 2.0}, // x, y, duration
        ActionOptions: option.ActionOptions{
            AntiRisk: true,
        },
    }

    request, err := tool.ConvertActionToCallToolRequest(action)
    assert.NoError(t, err)
    assert.Equal(t, string(option.ACTION_LongPress), request.Params.Name)
    assert.Equal(t, 100.0, request.Params.Arguments["x"])
    assert.Equal(t, 200.0, request.Params.Arguments["y"])
    assert.Equal(t, 2.0, request.Params.Arguments["duration"])
    assert.Equal(t, true, request.Params.Arguments["anti_risk"])
}

📋 工具开发最佳实践

1. 命名规范

  • 工具结构体: Tool{ActionName}
  • 常量定义: ACTION_{ActionName}
  • 参数名称: 使用下划线分隔 (from_x, to_y)

2. 参数验证

// 必需参数验证
if unifiedReq.Text == "" {
    return nil, fmt.Errorf("text parameter is required")
}

// 坐标参数验证
_, hasX := request.Params.Arguments["x"]
_, hasY := request.Params.Arguments["y"]
if !hasX || !hasY {
    return nil, fmt.Errorf("x and y coordinates are required")
}

3. 错误处理

// 统一错误格式
if err != nil {
    return mcp.NewToolResultError(fmt.Sprintf("操作失败: %s", err.Error())), nil
}

// 成功结果
return mcp.NewToolResultText(fmt.Sprintf("操作成功: %s", details)), nil

4. 日志记录

// 操作开始日志
log.Info().Str("action", "long_press").
    Float64("x", x).Float64("y", y).
    Msg("executing long press operation")

// 调试日志
log.Debug().Interface("arguments", arguments).
    Msg("parsed tool arguments")

5. 选项处理

// 使用 extractActionOptionsToArguments 统一处理
extractActionOptionsToArguments(action.GetOptions(), arguments)

// 或手动添加特定选项
if unifiedReq.AntiRisk {
    opts = append(opts, option.WithAntiRisk(true))
}

🚀 高级特性

1. 反作弊支持

// 在需要反作弊的操作中添加
if unifiedReq.AntiRisk {
    arguments := getCommonMCPArguments(driver)
    callMCPActionTool(driver, "evalpkgs", "set_touch_info", arguments)
}

2. 异步操作

// 对于长时间运行的操作,使用 context 控制超时
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

3. 批量操作

// 支持批量参数处理
for _, point := range unifiedReq.Points {
    err := driverExt.TapXY(point.X, point.Y, opts...)
    if err != nil {
        return mcp.NewToolResultError(fmt.Sprintf("批量操作失败: %s", err.Error())), nil
    }
}

📚 MCP Tools 快速参考

📱 设备管理工具

list_available_devices

功能: 发现所有可用的设备和模拟器 参数: 无 返回: JSON 格式的设备列表

{
  "androidDevices": ["emulator-5554", "device-serial"],
  "iosDevices": ["iPhone-UDID", "simulator-UDID"]
}

select_device

功能: 选择要使用的设备 参数:

  • platform (string): "android" | "ios" | "web" | "harmony"
  • serial (string): 设备序列号或 UDID

👆 触摸操作工具

tap_xy

功能: 在相对坐标点击 (0-1 范围) 参数:

  • x (number): X 坐标 (0.0-1.0)
  • y (number): Y 坐标 (0.0-1.0)
  • duration (number, 可选): 点击持续时间(秒)
  • anti_risk (boolean, 可选): 启用反作弊

tap_abs_xy

功能: 在绝对像素坐标点击 参数:

  • x (number): X 像素坐标
  • y (number): Y 像素坐标
  • duration (number, 可选): 点击持续时间(秒)
  • anti_risk (boolean, 可选): 启用反作弊

tap_ocr

功能: 通过 OCR 识别文本并点击 参数:

  • text (string): 要查找的文本
  • ignore_NotFoundError (boolean, 可选): 忽略未找到错误
  • regex (boolean, 可选): 使用正则表达式匹配

tap_cv

功能: 通过计算机视觉识别图像并点击 参数:

  • imagePath (string): 模板图像路径
  • threshold (number, 可选): 匹配阈值

double_tap_xy

功能: 在指定坐标双击 参数:

  • x (number): X 坐标
  • y (number): Y 坐标

🔄 手势操作工具

swipe

功能: 通用滑动 (自动检测方向或坐标) 参数: 支持方向滑动或坐标滑动两种模式

方向滑动模式:
  • direction (string): "up" | "down" | "left" | "right"
  • duration (number, 可选): 滑动持续时间
  • press_duration (number, 可选): 按压持续时间
坐标滑动模式:
  • from_x (number): 起始 X 坐标
  • from_y (number): 起始 Y 坐标
  • to_x (number): 结束 X 坐标
  • to_y (number): 结束 Y 坐标

drag

功能: 拖拽操作 参数:

  • from_x (number): 起始 X 坐标
  • from_y (number): 起始 Y 坐标
  • to_x (number): 结束 X 坐标
  • to_y (number): 结束 Y 坐标
  • duration (number, 可选): 拖拽持续时间(毫秒)

swipe_to_tap_app

功能: 滑动查找并点击应用 参数:

  • appName (string): 应用名称
  • max_retry_times (number, 可选): 最大重试次数
  • ignore_NotFoundError (boolean, 可选): 忽略未找到错误

swipe_to_tap_text

功能: 滑动查找并点击文本 参数:

  • text (string): 要查找的文本
  • max_retry_times (number, 可选): 最大重试次数
  • regex (boolean, 可选): 使用正则表达式

swipe_to_tap_texts

功能: 滑动查找并点击多个文本中的一个 参数:

  • texts (array): 文本数组
  • max_retry_times (number, 可选): 最大重试次数

⌨️ 输入操作工具

input

功能: 在当前焦点元素输入文本 参数:

  • text (string): 要输入的文本

press_button

功能: 按设备按键 参数:

  • button (string): 按键名称
    • Android: "BACK", "HOME", "VOLUME_UP", "VOLUME_DOWN", "ENTER"
    • iOS: "HOME", "VOLUME_UP", "VOLUME_DOWN"

home

功能: 按 Home 键 参数: 无

back

功能: 按返回键 (仅 Android) 参数: 无


📱 应用管理工具

list_packages

功能: 列出设备上所有应用包名 参数: 无

app_launch

功能: 启动应用 参数:

  • packageName (string): 应用包名

app_terminate

功能: 终止应用 参数:

  • packageName (string): 应用包名

app_install

功能: 安装应用 参数:

  • appUrl (string): APK/IPA 文件路径或 URL

app_uninstall

功能: 卸载应用 参数:

  • packageName (string): 应用包名

app_clear

功能: 清除应用数据 参数:

  • packageName (string): 应用包名

📸 屏幕操作工具

screenshot

功能: 截取屏幕截图 参数: 无 返回: Base64 编码的图像数据

get_screen_size

功能: 获取屏幕尺寸 参数: 无 返回: 屏幕宽度和高度 (像素)

get_source

功能: 获取 UI 层次结构 参数:

  • packageName (string, 可选): 指定应用包名

⏱️ 时间控制工具

sleep

功能: 等待指定秒数 参数:

  • seconds (number): 等待秒数

sleep_ms

功能: 等待指定毫秒数 参数:

  • milliseconds (number): 等待毫秒数

sleep_random

功能: 随机等待 参数:

  • params (array): 随机参数数组

🛠️ 实用工具

set_ime

功能: 设置输入法 参数:

  • ime (string): 输入法包名

close_popups

功能: 关闭弹窗 参数: 无


🌐 Web 操作工具

web_login_none_ui

功能: 无 UI 登录 参数:

  • packageName (string): 应用包名
  • phoneNumber (string, 可选): 手机号
  • captcha (string, 可选): 验证码
  • password (string, 可选): 密码

secondary_click

功能: 右键点击 参数:

  • x (number): X 坐标
  • y (number): Y 坐标

hover_by_selector

功能: 悬停在选择器元素上 参数:

  • selector (string): CSS 选择器或 XPath

tap_by_selector

功能: 点击选择器元素 参数:

  • selector (string): CSS 选择器或 XPath

secondary_click_by_selector

功能: 右键点击选择器元素 参数:

  • selector (string): CSS 选择器或 XPath

web_close_tab

功能: 关闭浏览器标签页 参数:

  • tabIndex (number): 标签页索引

🤖 AI 操作工具

ai_action

功能: AI 驱动的智能操作 参数:

  • prompt (string): 自然语言指令

finished

功能: 标记任务完成 参数:

  • content (string): 完成信息

📋 通用参数说明

设备参数 (所有工具通用)

  • platform (string): 设备平台
    • "android": Android 设备
    • "ios": iOS 设备
    • "web": Web 浏览器
    • "harmony": 鸿蒙设备
  • serial (string): 设备标识符
    • Android: 设备序列号 (如 "emulator-5554")
    • iOS: 设备 UDID
    • Web: 浏览器会话 ID

坐标参数

  • 相对坐标: 0.0-1.0 范围,相对于屏幕尺寸
  • 绝对坐标: 像素值,基于实际屏幕分辨率

时间参数

  • duration: 操作持续时间 (秒)
  • press_duration: 按压持续时间 (秒)
  • milliseconds: 毫秒数

行为参数

  • anti_risk: 启用反作弊检测
  • ignore_NotFoundError: 忽略元素未找到错误
  • regex: 使用正则表达式匹配
  • pre_mark_operation: 启用操作前标记 (用于调试和可视化)
  • max_retry_times: 最大重试次数
  • index: 元素索引 (多个匹配时)

🔧 使用示例

基本点击操作

{
  "name": "tap_xy",
  "arguments": {
    "platform": "android",
    "serial": "emulator-5554",
    "x": 0.5,
    "y": 0.3
  }
}

滑动操作

{
  "name": "swipe",
  "arguments": {
    "platform": "android",
    "serial": "emulator-5554",
    "direction": "up",
    "duration": 0.5
  }
}

应用启动

{
  "name": "app_launch",
  "arguments": {
    "platform": "android",
    "serial": "emulator-5554",
    "packageName": "com.example.app"
  }
}

OCR 文本点击

{
  "name": "tap_ocr",
  "arguments": {
    "platform": "android",
    "serial": "emulator-5554",
    "text": "登录",
    "ignore_NotFoundError": false
  }
}

⚠️ 注意事项

  1. 设备连接: 确保设备已连接并可访问
  2. 权限要求: 某些操作需要设备 root 或开发者权限
  3. 坐标系统: 注意相对坐标 (0-1) 和绝对坐标 (像素) 的区别
  4. 平台差异: 不同平台支持的功能可能有差异
  5. 错误处理: 建议启用适当的错误忽略选项
  6. 性能考虑: 避免过于频繁的操作,适当添加等待时间