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

603 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 协议通信和工具注册的主要服务器结构体:
```go
type MCPServer4XTDriver struct {
mcpServer *server.MCPServer // MCP 协议服务器
mcpTools []mcp.Tool // 注册的工具列表
actionToolMap map[option.ActionName]ActionTool // 动作到工具的映射
}
```
#### ActionTool 接口
定义所有 MCP 工具的契约:
```go
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 自动化工具
#### 使用示例
```go
// 创建和启动 MCP 服务器
server := NewMCPServer()
err := server.Start() // 阻塞并通过 stdio 提供 MCP 协议服务
```
#### 客户端交互流程
1. **初始化连接**: 建立 MCP 协议连接
2. **列出可用工具**: 获取所有注册的工具列表
3. **调用工具**: 使用参数调用特定工具
4. **接收结果**: 获取结构化的操作结果
## 🛠️ 实现原理
### 统一参数处理
使用 `parseActionOptions` 函数统一处理 MCP 请求参数:
```go
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` 函数实现设备的统一管理:
```go
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 工具都遵循统一的实现模式:
```go
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",
}
}
```
### 错误处理机制
统一的错误处理和日志记录:
```go
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.go``registerTools()` 方法中统一注册所有工具:
```go
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: 定义工具结构体
```go
// 新工具:长按操作
type ToolLongPress struct{}
func (t *ToolLongPress) Name() option.ActionName {
return option.ACTION_LongPress // 需要在 option 包中定义
}
func (t *ToolLongPress) Description() string {
return "在指定坐标执行长按操作"
}
```
#### 步骤 2: 定义 MCP 选项
```go
func (t *ToolLongPress) Options() []mcp.ToolOption {
unifiedReq := &option.ActionOptions{}
return unifiedReq.GetMCPOptions(option.ACTION_LongPress)
}
```
#### 步骤 3: 实现工具逻辑
```go
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: 实现动作转换和返回值结构
```go
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.go``registerTools()` 方法中添加:
```go
// 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}` 格式
#### 参数验证
```go
// 必需参数验证
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")
}
```
#### 错误处理
```go
// 统一错误格式
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("操作失败: %s", err.Error())), nil
}
// 成功结果
return mcp.NewToolResultText(fmt.Sprintf("操作成功: %s", details)), nil
```
#### 日志记录
```go
// 操作开始日志
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")
```
#### 返回值类型规范
```go
// 标准返回值类型前缀
"message": "string: 描述信息"
"x": "float64: X坐标值"
"count": "int: 数量"
"success": "bool: 成功状态"
"items": "[]string: 字符串数组"
"data": "object: 复杂对象"
```
## 🚀 性能与安全
### 性能考虑
- **驱动器实例缓存**: 为提高效率,驱动器实例被缓存和重用
- **参数解析优化**: 参数解析经过优化以最小化 JSON 开销
- **超时控制**: 超时控制防止操作挂起
- **资源清理**: 资源清理确保内存效率
- **模块化加载**: 按需加载工具模块,减少内存占用
### 安全注意事项
- **设备操作权限**: 所有设备操作都需要明确权限
- **输入验证**: 输入验证防止注入攻击
- **敏感操作保护**: 敏感操作支持反检测措施
- **审计日志**: 审计日志跟踪所有工具执行
### 高级特性
#### 反作弊支持
```go
// 在需要反作弊的操作中添加
if unifiedReq.AntiRisk {
arguments := getCommonMCPArguments(driver)
callMCPActionTool(driver, "evalpkgs", "set_touch_info", arguments)
}
```
#### 异步操作
```go
// 对于长时间运行的操作,使用 context 控制超时
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
```
#### 批量操作
```go
// 支持批量参数处理
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 服务器解决方案。