Files
httprunner/uixt/mcp_server.md
lilong.129 1cc4b1cf5b refactor: modularize MCP server tools by functionality
- Split large mcp_server.go into modular files by functionality
- Create dedicated files for each tool category:
  - mcp_tools_device.go: Device management tools
  - mcp_tools_touch.go: Touch operation tools  
  - mcp_tools_swipe.go: Swipe and drag operation tools
  - mcp_tools_input.go: Input and IME tools
  - mcp_tools_button.go: Button operation tools
  - mcp_tools_app.go: Application management tools
  - mcp_tools_screen.go: Screen operation tools
  - mcp_tools_utility.go: Utility tools (sleep, popups)
  - mcp_tools_web.go: Web operation tools
  - mcp_tools_ai.go: AI-driven operation tools
- Update mcp_server.md documentation to reflect modular architecture
- Maintain pure ActionTool architecture with complete tool decoupling
- Improve code organization and maintainability
2025-06-03 15:45:42 +08:00

19 KiB
Raw Blame History

HttpRunner MCP Server 完整说明文档

📖 概述

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

🏗️ 架构设计

整体架构

MCP 服务器采用纯 ActionTool 架构,其中每个 UI 操作都作为独立的工具实现,符合 ActionTool 接口规范:

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

核心组件

MCPServer4XTDriver

管理 MCP 协议通信和工具注册的主要服务器结构体:

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

ActionTool 接口

定义所有 MCP 工具的契约:

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

模块化架构

为了更好的代码组织和维护MCP 工具按功能类别拆分为多个文件:

  • mcp_server.go: 核心服务器实现和工具注册
  • mcp_tools_device.go: 设备管理工具
  • mcp_tools_touch.go: 触摸操作工具
  • mcp_tools_swipe.go: 滑动和拖拽操作工具
  • mcp_tools_input.go: 输入和 IME 工具
  • mcp_tools_button.go: 按键操作工具
  • mcp_tools_app.go: 应用管理工具
  • mcp_tools_screen.go: 屏幕操作工具
  • mcp_tools_utility.go: 实用工具(睡眠、弹窗等)
  • mcp_tools_web.go: Web 操作工具
  • mcp_tools_ai.go: AI 驱动操作工具

架构特点

纯 ActionTool 架构实现

  • 每个 MCP 工具都是实现 ActionTool 接口的独立结构体
  • 操作逻辑直接嵌入在每个工具的 Implement() 方法中
  • 工具间无中间动作方法或耦合关系
  • 完全解耦,摆脱了原有大型 switch-case DoAction 方法

架构流程

MCP Request -> ActionTool.Implement() -> Direct Driver Method Call

架构优势

  • 真正的 ActionTool 接口一致性: 所有工具保持一致
  • 完全解耦: 无方法间依赖关系
  • 模块化组织: 按功能分类的文件结构
  • 简化错误处理: 每个工具独立的错误处理和日志记录
  • 易于扩展: 新功能易于扩展

🎯 功能特性

支持的操作类别

设备管理mcp_tools_device.go

  • list_available_devices: 发现 Android/iOS 设备和模拟器
  • select_device: 通过平台和序列号选择特定设备

触摸操作mcp_tools_touch.go

  • tap_xy: 在相对坐标点击 (0-1 范围)
  • tap_abs_xy: 在绝对像素坐标点击
  • tap_ocr: 通过 OCR 识别文本并点击
  • tap_cv: 通过计算机视觉识别元素并点击
  • double_tap_xy: 在坐标处双击

手势操作mcp_tools_swipe.go

  • swipe: 通用滑动,自动检测方向或坐标
  • swipe_direction: 方向滑动 (上/下/左/右)
  • swipe_coordinate: 基于坐标的精确滑动控制
  • drag: 两点间的拖拽操作
  • swipe_to_tap_app: 滑动查找并点击应用
  • swipe_to_tap_text: 滑动查找并点击文本
  • swipe_to_tap_texts: 滑动查找并点击多个文本中的一个

输入操作mcp_tools_input.go

  • input: 在焦点元素上输入文本
  • set_ime: 设置输入法编辑器

按键操作mcp_tools_button.go

  • press_button: 按设备按键 (home、back、音量等)
  • home: 按 home 键
  • back: 按 back 键

应用管理mcp_tools_app.go

  • list_packages: 列出所有已安装应用
  • app_launch: 通过包名启动应用
  • app_terminate: 终止运行中的应用
  • app_install: 从 URL/路径安装应用
  • app_uninstall: 通过包名卸载应用
  • app_clear: 清除应用数据和缓存

屏幕操作mcp_tools_screen.go

  • screenshot: 捕获屏幕为 Base64 编码图像
  • get_screen_size: 获取设备屏幕尺寸
  • get_source: 获取 UI 层次结构/源码

实用工具操作mcp_tools_utility.go

  • sleep: 等待指定秒数
  • sleep_ms: 等待指定毫秒数
  • sleep_random: 基于参数的随机等待
  • close_popups: 关闭弹窗/对话框

Web 操作mcp_tools_web.go

  • web_login_none_ui: 执行无 UI 交互的登录
  • secondary_click: 在指定坐标右键点击
  • hover_by_selector: 通过 CSS 选择器/XPath 悬停元素
  • tap_by_selector: 通过 CSS 选择器/XPath 点击元素
  • secondary_click_by_selector: 通过选择器右键点击元素
  • web_close_tab: 通过索引关闭浏览器标签页

AI 操作mcp_tools_ai.go

  • ai_action: 使用自然语言提示执行 AI 驱动的动作
  • finished: 标记任务完成并返回结果消息

关键特性

反作弊支持

为敏感操作内置反检测机制:

  • 真实时间的触摸模拟
  • 设备指纹掩码
  • 行为模式随机化

统一参数处理

所有工具通过 parseActionOptions() 使用一致的参数解析:

  • 类型安全的 JSON 编组/解组
  • 自动验证和错误处理
  • 支持复杂嵌套参数

设备抽象

无缝的多平台支持:

  • 通过 ADB 支持 Android 设备
  • 通过 go-ios 支持 iOS 设备
  • 通过 WebDriver 支持 Web 浏览器
  • 支持 Harmony OS 设备

错误处理

全面的错误管理:

  • 结构化错误响应
  • 带上下文的详细日志记录
  • 优雅的故障恢复

📖 使用指南

创建和启动服务器

NewMCPServer 函数

该函数创建一个新的 XTDriver MCP 服务器并注册所有工具:

  • MCP 协议服务器: 具有 uixt 功能
  • 版本信息: 来自 HttpRunner
  • 工具功能: 为性能考虑禁用 (设置为 false)
  • 预注册工具: 所有可用的 UI 自动化工具

使用示例

// 创建和启动 MCP 服务器
server := NewMCPServer()
err := server.Start() // 阻塞并通过 stdio 提供 MCP 协议服务

客户端交互流程

  1. 初始化连接: 建立 MCP 协议连接
  2. 列出可用工具: 获取所有注册的工具列表
  3. 调用工具: 使用参数调用特定工具
  4. 接收结果: 获取结构化的操作结果

🛠️ 实现原理

统一参数处理

使用 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
}

设备管理策略

通过 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
}

工具实现模式

每个 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
    }
}

func (t *ToolTapXY) ReturnSchema() map[string]string {
    return map[string]string{
        "message": "string: Success message confirming tap operation at specified coordinates",
    }
}

错误处理机制

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

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

工具注册机制

mcp_server.goregisterTools() 方法中统一注册所有工具:

func (s *MCPServer4XTDriver) registerTools() {
    // Device Tools
    s.registerTool(&ToolListAvailableDevices{})
    s.registerTool(&ToolSelectDevice{})

    // Touch Tools
    s.registerTool(&ToolTapXY{})
    s.registerTool(&ToolTapAbsXY{})
    s.registerTool(&ToolTapByOCR{})
    s.registerTool(&ToolTapByCV{})
    s.registerTool(&ToolDoubleTapXY{})

    // Swipe Tools
    s.registerTool(&ToolSwipe{})
    s.registerTool(&ToolSwipeDirection{})
    s.registerTool(&ToolSwipeCoordinate{})
    s.registerTool(&ToolSwipeToTapApp{})
    s.registerTool(&ToolSwipeToTapText{})
    s.registerTool(&ToolSwipeToTapTexts{})
    s.registerTool(&ToolDrag{})

    // Input Tools
    s.registerTool(&ToolInput{})
    s.registerTool(&ToolSetIme{})

    // Button Tools
    s.registerTool(&ToolPressButton{})
    s.registerTool(&ToolHome{})
    s.registerTool(&ToolBack{})

    // App Tools
    s.registerTool(&ToolListPackages{})
    s.registerTool(&ToolLaunchApp{})
    s.registerTool(&ToolTerminateApp{})
    s.registerTool(&ToolAppInstall{})
    s.registerTool(&ToolAppUninstall{})
    s.registerTool(&ToolAppClear{})

    // Screen Tools
    s.registerTool(&ToolScreenShot{})
    s.registerTool(&ToolGetScreenSize{})
    s.registerTool(&ToolGetSource{})

    // Utility Tools
    s.registerTool(&ToolSleep{})
    s.registerTool(&ToolSleepMS{})
    s.registerTool(&ToolSleepRandom{})
    s.registerTool(&ToolClosePopups{})

    // Web Tools
    s.registerTool(&ToolWebLoginNoneUI{})
    s.registerTool(&ToolSecondaryClick{})
    s.registerTool(&ToolHoverBySelector{})
    s.registerTool(&ToolTapBySelector{})
    s.registerTool(&ToolSecondaryClickBySelector{})
    s.registerTool(&ToolWebCloseTab{})

    // AI Tools
    s.registerTool(&ToolAIAction{})
    s.registerTool(&ToolFinished{})
}

🔧 扩展开发

添加新工具的步骤

  1. 选择合适的文件: 根据功能类别选择对应的 mcp_tools_*.go 文件
  2. 定义工具结构体: 实现 ActionTool 接口
  3. 实现所有必需方法: Name、Description、Options、Implement、ConvertActionToCallToolRequest、ReturnSchema
  4. 在 registerTools() 方法中注册工具
  5. 添加全面的单元测试
  6. 更新文档

开发示例:长按操作工具

假设要在 mcp_tools_touch.go 中添加长按操作:

步骤 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 {
    unifiedReq := &option.ActionOptions{}
    return unifiedReq.GetMCPOptions(option.ACTION_LongPress)
}

步骤 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)
}

func (t *ToolLongPress) ReturnSchema() map[string]string {
    return map[string]string{
        "message":  "string: Success message confirming long press operation",
        "x":        "float64: X coordinate where long press was performed",
        "y":        "float64: Y coordinate where long press was performed",
        "duration": "float64: Duration of the long press in seconds",
    }
}

步骤 5: 注册工具

mcp_server.goregisterTools() 方法中添加:

// Touch Tools
s.registerTool(&ToolTapXY{})
s.registerTool(&ToolTapAbsXY{})
s.registerTool(&ToolTapByOCR{})
s.registerTool(&ToolTapByCV{})
s.registerTool(&ToolDoubleTapXY{})
s.registerTool(&ToolLongPress{}) // 新增长按工具

开发最佳实践

文件组织规范

  • 按功能分类: 将相关工具放在同一个文件中
  • 命名一致性: 文件名使用 mcp_tools_{category}.go 格式
  • 工具命名: 结构体使用 Tool{ActionName} 格式

参数验证

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

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

错误处理

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

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

日志记录

// 操作开始日志
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")

返回值类型规范

// 标准返回值类型前缀
"message": "string: 描述信息"
"x": "float64: X坐标值"
"count": "int: 数量"
"success": "bool: 成功状态"
"items": "[]string: 字符串数组"
"data": "object: 复杂对象"

🚀 性能与安全

性能考虑

  • 驱动器实例缓存: 为提高效率,驱动器实例被缓存和重用
  • 参数解析优化: 参数解析经过优化以最小化 JSON 开销
  • 超时控制: 超时控制防止操作挂起
  • 资源清理: 资源清理确保内存效率
  • 模块化加载: 按需加载工具模块,减少内存占用

安全注意事项

  • 设备操作权限: 所有设备操作都需要明确权限
  • 输入验证: 输入验证防止注入攻击
  • 敏感操作保护: 敏感操作支持反检测措施
  • 审计日志: 审计日志跟踪所有工具执行

高级特性

反作弊支持

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

异步操作

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

批量操作

// 支持批量参数处理
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
    }
}

📚 总结

HttpRunner MCP Server 通过模块化的架构设计,将 UI 自动化功能按类别拆分为多个文件,每个文件专注于特定的功能领域。这种设计不仅提高了代码的可维护性和可扩展性,还使得开发者能够更容易地理解和贡献代码。

核心优势

  1. 模块化架构: 按功能分类的文件组织,便于维护和扩展
  2. 统一接口: 所有工具都实现相同的 ActionTool 接口
  3. 类型安全: 强类型的参数处理和返回值定义
  4. 完整文档: 每个工具都有详细的参数和返回值说明
  5. 易于测试: 独立的工具实现便于单元测试

该实现为 UI 自动化测试提供了一个完整、可扩展且高性能的 MCP 服务器解决方案。