mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-06 20:32:44 +08:00
feat: optimize MCP tools response format with automatic schema generation
- Remove all manual ReturnSchema() methods from tools - Implement automatic schema generation using reflection - Unify response format to flat structure with action/success/message fields - Simplify tool implementation by removing MCPResponse embedding - Update documentation to reflect new architecture - Achieve ~70% code reduction while maintaining type safety
This commit is contained in:
@@ -1 +1 @@
|
||||
v5.0.0-beta-2506052026
|
||||
v5.0.0-beta-2506052317
|
||||
|
||||
@@ -161,7 +161,7 @@ func (host *MCPHost) convertSingleToolToRecord(serverName string, tool mcp.Tool,
|
||||
return MCPToolRecord{
|
||||
ToolID: id,
|
||||
VisibleRange: 1,
|
||||
ToolType: "edge",
|
||||
ToolType: "Hrp",
|
||||
ServerName: serverName,
|
||||
ToolName: tool.Name,
|
||||
Description: info.Description,
|
||||
@@ -227,7 +227,7 @@ func (host *MCPHost) extractReturns(serverName, toolName string, info DocStringI
|
||||
// Priority 1: Get from ActionTool interface if available
|
||||
if actionToolProvider := host.getActionToolProvider(serverName); actionToolProvider != nil {
|
||||
if actionTool := actionToolProvider.GetToolByAction(option.ActionName(toolName)); actionTool != nil {
|
||||
returnSchema := actionTool.ReturnSchema()
|
||||
returnSchema := uixt.GenerateReturnSchema(actionTool)
|
||||
if len(returnSchema) > 0 {
|
||||
return host.marshalToJSON(returnSchema, "return schema")
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package uixt
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
@@ -150,8 +152,6 @@ type ActionTool interface {
|
||||
Implement() server.ToolHandlerFunc
|
||||
// ConvertActionToCallToolRequest converts MobileAction to mcp.CallToolRequest
|
||||
ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error)
|
||||
// ReturnSchema returns the expected return value schema based on mcp.CallToolResult conventions
|
||||
ReturnSchema() map[string]string
|
||||
}
|
||||
|
||||
// buildMCPCallToolRequest is a helper function to build mcp.CallToolRequest
|
||||
@@ -246,3 +246,175 @@ func parseActionOptions(arguments map[string]any) (*option.ActionOptions, error)
|
||||
|
||||
return &actionOptions, nil
|
||||
}
|
||||
|
||||
// MCPResponse represents the standard response structure for all MCP tools
|
||||
type MCPResponse struct {
|
||||
Action string `json:"action" desc:"Action performed"`
|
||||
Success bool `json:"success" desc:"Whether the operation was successful"`
|
||||
Message string `json:"message" desc:"Human-readable message describing the result"`
|
||||
}
|
||||
|
||||
// NewMCPSuccessResponse creates a successful response with structured data
|
||||
func NewMCPSuccessResponse(message string, actionTool ActionTool) *mcp.CallToolResult {
|
||||
// Create base response with standard fields
|
||||
response := map[string]any{
|
||||
"action": string(actionTool.Name()),
|
||||
"success": true,
|
||||
"message": message,
|
||||
}
|
||||
|
||||
// Add all tool-specific fields at the same level
|
||||
toolData := convertToolToData(actionTool)
|
||||
for key, value := range toolData {
|
||||
response[key] = value
|
||||
}
|
||||
|
||||
return marshalToMCPResult(response)
|
||||
}
|
||||
|
||||
// convertToolToData converts tool struct to map[string]any for Data field
|
||||
func convertToolToData(tool interface{}) map[string]any {
|
||||
data := make(map[string]any)
|
||||
|
||||
// Use reflection to extract fields from the tool struct
|
||||
structValue := reflect.ValueOf(tool)
|
||||
structType := reflect.TypeOf(tool)
|
||||
|
||||
// Handle pointer types
|
||||
if structType.Kind() == reflect.Ptr {
|
||||
structValue = structValue.Elem()
|
||||
structType = structType.Elem()
|
||||
}
|
||||
|
||||
// Extract all fields except MCPResponse
|
||||
for i := 0; i < structType.NumField(); i++ {
|
||||
field := structType.Field(i)
|
||||
fieldValue := structValue.Field(i)
|
||||
|
||||
// Skip MCPResponse embedded fields
|
||||
if field.Type.Name() == "MCPResponse" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Get JSON tag name
|
||||
jsonTag := field.Tag.Get("json")
|
||||
if jsonTag == "" || jsonTag == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse JSON tag (remove omitempty, etc.)
|
||||
jsonName := strings.Split(jsonTag, ",")[0]
|
||||
if jsonName == "" {
|
||||
jsonName = strings.ToLower(field.Name)
|
||||
}
|
||||
|
||||
// Add field value to data
|
||||
if fieldValue.IsValid() && fieldValue.CanInterface() {
|
||||
data[jsonName] = fieldValue.Interface()
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// NewMCPErrorResponse creates an error response
|
||||
func NewMCPErrorResponse(message string) *mcp.CallToolResult {
|
||||
response := map[string]any{
|
||||
"success": false,
|
||||
"message": message,
|
||||
}
|
||||
return marshalToMCPResult(response)
|
||||
}
|
||||
|
||||
// marshalToMCPResult converts any data to mcp.CallToolResult
|
||||
func marshalToMCPResult(data interface{}) *mcp.CallToolResult {
|
||||
jsonData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
// Fallback to error response if marshaling fails
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Failed to marshal response: %s", err.Error()))
|
||||
}
|
||||
return mcp.NewToolResultText(string(jsonData))
|
||||
}
|
||||
|
||||
// GenerateReturnSchema generates return schema from a struct using reflection
|
||||
func GenerateReturnSchema(toolStruct interface{}) map[string]string {
|
||||
schema := make(map[string]string)
|
||||
|
||||
// Add standard MCPResponse fields
|
||||
schema["action"] = "string: Action performed"
|
||||
schema["success"] = "boolean: Whether the operation was successful"
|
||||
schema["message"] = "string: Human-readable message describing the result"
|
||||
|
||||
// Get the type of the struct
|
||||
structType := reflect.TypeOf(toolStruct)
|
||||
if structType.Kind() == reflect.Ptr {
|
||||
structType = structType.Elem()
|
||||
}
|
||||
|
||||
// Iterate through all fields and add them at the same level
|
||||
for i := 0; i < structType.NumField(); i++ {
|
||||
field := structType.Field(i)
|
||||
|
||||
// Skip embedded MCPResponse fields (though they shouldn't exist now)
|
||||
if field.Type.Name() == "MCPResponse" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Get JSON tag
|
||||
jsonTag := field.Tag.Get("json")
|
||||
if jsonTag == "" || jsonTag == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse JSON tag (remove omitempty, etc.)
|
||||
jsonName := strings.Split(jsonTag, ",")[0]
|
||||
if jsonName == "" {
|
||||
jsonName = strings.ToLower(field.Name)
|
||||
}
|
||||
|
||||
// Get description from tag
|
||||
description := field.Tag.Get("desc")
|
||||
if description == "" {
|
||||
description = fmt.Sprintf("%s field", field.Name)
|
||||
}
|
||||
|
||||
// Get field type
|
||||
fieldType := getFieldTypeString(field.Type)
|
||||
|
||||
// Add to schema at the same level as standard fields
|
||||
schema[jsonName] = fmt.Sprintf("%s: %s", fieldType, description)
|
||||
}
|
||||
|
||||
return schema
|
||||
}
|
||||
|
||||
// getFieldTypeString converts Go type to string representation
|
||||
func getFieldTypeString(t reflect.Type) string {
|
||||
switch t.Kind() {
|
||||
case reflect.String:
|
||||
return "string"
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return "int"
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return "uint"
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return "float64"
|
||||
case reflect.Bool:
|
||||
return "boolean"
|
||||
case reflect.Slice:
|
||||
elemType := getFieldTypeString(t.Elem())
|
||||
return fmt.Sprintf("[]%s", elemType)
|
||||
case reflect.Map:
|
||||
keyType := getFieldTypeString(t.Key())
|
||||
valueType := getFieldTypeString(t.Elem())
|
||||
return fmt.Sprintf("map[%s]%s", keyType, valueType)
|
||||
case reflect.Struct:
|
||||
return "object"
|
||||
case reflect.Ptr:
|
||||
return getFieldTypeString(t.Elem())
|
||||
case reflect.Interface:
|
||||
return "interface{}"
|
||||
default:
|
||||
return t.String()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
## 📖 概述
|
||||
|
||||
HttpRunner MCP Server 是基于 Model Context Protocol (MCP) 协议实现的 UI 自动化测试服务器,它将 HttpRunner 的强大 UI 自动化能力通过标准化的 MCP 接口暴露给 AI 模型和其他客户端,使其能够执行移动端和 Web 端的 UI 自动化任务。
|
||||
HttpRunner MCP Server 是基于 Model Context Protocol (MCP) 协议实现的 UI 自动化测试服务器,将 HttpRunner 的强大 UI 自动化能力通过标准化的 MCP 接口暴露给 AI 模型和其他客户端,支持移动端和 Web 端的 UI 自动化任务。
|
||||
|
||||
## 🏗️ 架构设计
|
||||
|
||||
### 整体架构
|
||||
|
||||
MCP 服务器采用纯 ActionTool 架构,其中每个 UI 操作都作为独立的工具实现,符合 ActionTool 接口规范:
|
||||
采用纯 ActionTool 架构,每个 UI 操作都作为独立的工具实现:
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
@@ -26,7 +26,7 @@ MCP 服务器采用纯 ActionTool 架构,其中每个 UI 操作都作为独立
|
||||
### 核心组件
|
||||
|
||||
#### MCPServer4XTDriver
|
||||
管理 MCP 协议通信和工具注册的主要服务器结构体:
|
||||
MCP 协议服务器主体:
|
||||
|
||||
```go
|
||||
type MCPServer4XTDriver struct {
|
||||
@@ -37,7 +37,7 @@ type MCPServer4XTDriver struct {
|
||||
```
|
||||
|
||||
#### ActionTool 接口
|
||||
定义所有 MCP 工具的契约:
|
||||
所有 MCP 工具的统一契约:
|
||||
|
||||
```go
|
||||
type ActionTool interface {
|
||||
@@ -46,13 +46,12 @@ type ActionTool interface {
|
||||
Options() []mcp.ToolOption // MCP 选项定义
|
||||
Implement() server.ToolHandlerFunc // 工具实现逻辑
|
||||
ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) // 动作转换
|
||||
ReturnSchema() map[string]string // 返回值结构描述
|
||||
}
|
||||
```
|
||||
|
||||
### 模块化架构
|
||||
|
||||
为了更好的代码组织和维护,MCP 工具按功能类别拆分为多个文件:
|
||||
MCP 工具按功能类别拆分为多个文件:
|
||||
|
||||
- **mcp_server.go**: 核心服务器实现和工具注册
|
||||
- **mcp_tools_device.go**: 设备管理工具
|
||||
@@ -68,23 +67,70 @@ type ActionTool interface {
|
||||
|
||||
### 架构特点
|
||||
|
||||
#### 纯 ActionTool 架构实现
|
||||
- **每个 MCP 工具都是实现 ActionTool 接口的独立结构体**
|
||||
- **操作逻辑直接嵌入在每个工具的 Implement() 方法中**
|
||||
- **工具间无中间动作方法或耦合关系**
|
||||
- **完全解耦,摆脱了原有大型 switch-case DoAction 方法**
|
||||
- **完全解耦**: 每个工具独立实现,无依赖关系
|
||||
- **统一接口**: 所有工具遵循相同的 ActionTool 接口
|
||||
- **模块化组织**: 按功能分类的清晰文件结构
|
||||
- **直接调用**: `MCP Request -> ActionTool.Implement() -> Driver Method`
|
||||
|
||||
#### 架构流程
|
||||
```
|
||||
MCP Request -> ActionTool.Implement() -> Direct Driver Method Call
|
||||
## 📋 响应格式
|
||||
|
||||
### 扁平化响应结构
|
||||
|
||||
所有工具使用统一的扁平化响应格式,所有字段在同一层级:
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "list_packages",
|
||||
"success": true,
|
||||
"message": "Found 5 installed packages",
|
||||
"packages": ["com.example.app1", "com.example.app2"],
|
||||
"count": 2
|
||||
}
|
||||
```
|
||||
|
||||
#### 架构优势
|
||||
- **真正的 ActionTool 接口一致性**: 所有工具保持一致
|
||||
- **完全解耦**: 无方法间依赖关系
|
||||
- **模块化组织**: 按功能分类的文件结构
|
||||
- **简化错误处理**: 每个工具独立的错误处理和日志记录
|
||||
- **易于扩展**: 新功能易于扩展
|
||||
### 标准字段
|
||||
|
||||
每个响应包含三个标准字段:
|
||||
- **action**: 执行的操作名称
|
||||
- **success**: 操作是否成功(布尔值)
|
||||
- **message**: 人类可读的结果描述
|
||||
|
||||
### 工具特定字段
|
||||
|
||||
每个工具根据功能返回特定数据字段,与标准字段在同一层级。
|
||||
|
||||
### 响应创建
|
||||
|
||||
统一的响应创建函数:
|
||||
|
||||
```go
|
||||
func NewMCPSuccessResponse(message string, actionTool ActionTool) *mcp.CallToolResult
|
||||
```
|
||||
|
||||
该函数自动:
|
||||
- 提取操作名称
|
||||
- 设置成功状态
|
||||
- 使用反射提取工具字段
|
||||
- 创建扁平化响应
|
||||
|
||||
### 工具结构定义
|
||||
|
||||
工具结构体只包含返回数据字段:
|
||||
|
||||
```go
|
||||
type ToolListPackages struct {
|
||||
Packages []string `json:"packages" desc:"List of installed app package names on the device"`
|
||||
Count int `json:"count" desc:"Number of installed packages"`
|
||||
}
|
||||
```
|
||||
|
||||
### 自动模式生成
|
||||
|
||||
使用反射自动生成返回模式:
|
||||
|
||||
```go
|
||||
func GenerateReturnSchema(toolStruct interface{}) map[string]string
|
||||
```
|
||||
|
||||
## 🎯 功能特性
|
||||
|
||||
@@ -147,6 +193,7 @@ MCP Request -> ActionTool.Implement() -> Direct Driver Method Call
|
||||
- **web_close_tab**: 通过索引关闭浏览器标签页
|
||||
|
||||
#### AI 操作(mcp_tools_ai.go)
|
||||
- **start_to_goal**: 使用自然语言描述开始到目标的任务
|
||||
- **ai_action**: 使用自然语言提示执行 AI 驱动的动作
|
||||
- **finished**: 标记任务完成并返回结果消息
|
||||
|
||||
@@ -159,17 +206,17 @@ MCP Request -> ActionTool.Implement() -> Direct Driver Method Call
|
||||
- 行为模式随机化
|
||||
|
||||
#### 统一参数处理
|
||||
所有工具通过 parseActionOptions() 使用一致的参数解析:
|
||||
所有工具通过 `parseActionOptions()` 使用一致的参数解析:
|
||||
- 类型安全的 JSON 编组/解组
|
||||
- 自动验证和错误处理
|
||||
- 支持复杂嵌套参数
|
||||
|
||||
#### 设备抽象
|
||||
无缝的多平台支持:
|
||||
- 通过 ADB 支持 Android 设备
|
||||
- 通过 go-ios 支持 iOS 设备
|
||||
- 通过 WebDriver 支持 Web 浏览器
|
||||
- 支持 Harmony OS 设备
|
||||
- Android 设备(通过 ADB)
|
||||
- iOS 设备(通过 go-ios)
|
||||
- Web 浏览器(通过 WebDriver)
|
||||
- Harmony OS 设备
|
||||
|
||||
#### 错误处理
|
||||
全面的错误管理:
|
||||
@@ -181,422 +228,279 @@ MCP Request -> ActionTool.Implement() -> Direct Driver Method Call
|
||||
|
||||
### 创建和启动服务器
|
||||
|
||||
#### 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
|
||||
}
|
||||
```
|
||||
2. **工具发现**: 客户端查询可用工具列表
|
||||
3. **工具调用**: 客户端调用特定工具执行操作
|
||||
4. **响应处理**: 服务器返回结构化响应
|
||||
|
||||
### 工具实现模式
|
||||
|
||||
每个 MCP 工具都遵循统一的实现模式:
|
||||
每个工具遵循一致的实现模式:
|
||||
|
||||
```go
|
||||
type ToolTapXY struct{}
|
||||
|
||||
func (t *ToolTapXY) Name() option.ActionName {
|
||||
return option.ACTION_TapXY
|
||||
type ToolExample struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
Field1 string `json:"field1" desc:"Description of field1"`
|
||||
Field2 int `json:"field2" desc:"Description of field2"`
|
||||
}
|
||||
|
||||
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 *ToolExample) Name() option.ActionName {
|
||||
return option.ACTION_Example
|
||||
}
|
||||
|
||||
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 *ToolExample) Description() string {
|
||||
return "Description of what this tool does"
|
||||
}
|
||||
|
||||
func (t *ToolLongPress) Description() string {
|
||||
return "在指定坐标执行长按操作"
|
||||
}
|
||||
```
|
||||
|
||||
#### 步骤 2: 定义 MCP 选项
|
||||
|
||||
```go
|
||||
func (t *ToolLongPress) Options() []mcp.ToolOption {
|
||||
func (t *ToolExample) Options() []mcp.ToolOption {
|
||||
unifiedReq := &option.ActionOptions{}
|
||||
return unifiedReq.GetMCPOptions(option.ACTION_LongPress)
|
||||
return unifiedReq.GetMCPOptions(option.ACTION_Example)
|
||||
}
|
||||
```
|
||||
|
||||
#### 步骤 3: 实现工具逻辑
|
||||
|
||||
```go
|
||||
func (t *ToolLongPress) Implement() server.ToolHandlerFunc {
|
||||
func (t *ToolExample) Implement() server.ToolHandlerFunc {
|
||||
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
// 1. 设置驱动器
|
||||
// Setup driver
|
||||
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("setup driver failed: %w", err)
|
||||
}
|
||||
|
||||
// 2. 解析参数
|
||||
// Parse parameters
|
||||
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")
|
||||
// Execute business logic
|
||||
// ... implementation ...
|
||||
|
||||
// Create response
|
||||
message := "Operation completed successfully"
|
||||
returnData := ToolExample{
|
||||
Field1: "value1",
|
||||
Field2: 42,
|
||||
}
|
||||
|
||||
// 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
|
||||
return NewMCPSuccessResponse(message, &returnData), 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",
|
||||
func (t *ToolExample) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) {
|
||||
// Convert action to MCP request
|
||||
arguments := map[string]any{
|
||||
"param1": action.Params,
|
||||
}
|
||||
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
||||
}
|
||||
```
|
||||
|
||||
#### 步骤 5: 注册工具
|
||||
### 参数处理
|
||||
|
||||
在 `mcp_server.go` 的 `registerTools()` 方法中添加:
|
||||
#### 统一参数结构
|
||||
所有工具使用 `option.ActionOptions` 结构进行参数处理:
|
||||
|
||||
```go
|
||||
// Touch Tools
|
||||
s.registerTool(&ToolTapXY{})
|
||||
s.registerTool(&ToolTapAbsXY{})
|
||||
s.registerTool(&ToolTapByOCR{})
|
||||
s.registerTool(&ToolTapByCV{})
|
||||
s.registerTool(&ToolDoubleTapXY{})
|
||||
s.registerTool(&ToolLongPress{}) // 新增长按工具
|
||||
```
|
||||
type ActionOptions struct {
|
||||
// Common fields
|
||||
Platform string `json:"platform,omitempty"`
|
||||
Serial string `json:"serial,omitempty"`
|
||||
|
||||
### 开发最佳实践
|
||||
|
||||
#### 文件组织规范
|
||||
- **按功能分类**: 将相关工具放在同一个文件中
|
||||
- **命名一致性**: 文件名使用 `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")
|
||||
// Action-specific fields
|
||||
Text string `json:"text,omitempty"`
|
||||
X float64 `json:"x,omitempty"`
|
||||
Y float64 `json:"y,omitempty"`
|
||||
// ... more fields
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误处理
|
||||
#### 参数解析
|
||||
使用 `parseActionOptions()` 函数进行类型安全的参数解析:
|
||||
|
||||
```go
|
||||
// 统一错误格式
|
||||
unifiedReq, err := parseActionOptions(request.Params.Arguments)
|
||||
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)
|
||||
return nil, err
|
||||
}
|
||||
```
|
||||
|
||||
#### 异步操作
|
||||
### 错误处理
|
||||
|
||||
#### 错误响应
|
||||
使用 `NewMCPErrorResponse()` 创建错误响应:
|
||||
|
||||
```go
|
||||
// 对于长时间运行的操作,使用 context 控制超时
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
if err != nil {
|
||||
return NewMCPErrorResponse(fmt.Sprintf("Operation failed: %s", err.Error())), nil
|
||||
}
|
||||
```
|
||||
|
||||
#### 批量操作
|
||||
#### 错误响应格式
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "Error description"
|
||||
}
|
||||
```
|
||||
|
||||
## 🔧 开发指南
|
||||
|
||||
### 添加新工具
|
||||
|
||||
1. **定义工具结构体**:
|
||||
```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
|
||||
type ToolNewFeature struct {
|
||||
// Return data fields
|
||||
Result string `json:"result" desc:"Description of result"`
|
||||
}
|
||||
```
|
||||
|
||||
2. **实现 ActionTool 接口**:
|
||||
```go
|
||||
func (t *ToolNewFeature) Name() option.ActionName {
|
||||
return option.ACTION_NewFeature
|
||||
}
|
||||
|
||||
func (t *ToolNewFeature) Description() string {
|
||||
return "Description of the new feature"
|
||||
}
|
||||
|
||||
func (t *ToolNewFeature) Options() []mcp.ToolOption {
|
||||
unifiedReq := &option.ActionOptions{}
|
||||
return unifiedReq.GetMCPOptions(option.ACTION_NewFeature)
|
||||
}
|
||||
|
||||
func (t *ToolNewFeature) Implement() server.ToolHandlerFunc {
|
||||
// Implementation logic
|
||||
}
|
||||
|
||||
func (t *ToolNewFeature) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) {
|
||||
// Conversion logic
|
||||
}
|
||||
```
|
||||
|
||||
3. **注册工具**:
|
||||
在 `mcp_server.go` 的 `NewMCPServer()` 函数中添加:
|
||||
|
||||
```go
|
||||
&ToolNewFeature{},
|
||||
```
|
||||
|
||||
### 测试工具
|
||||
|
||||
#### 单元测试
|
||||
```go
|
||||
func TestToolNewFeature(t *testing.T) {
|
||||
tool := &ToolNewFeature{}
|
||||
|
||||
// Test Name
|
||||
assert.Equal(t, option.ACTION_NewFeature, tool.Name())
|
||||
|
||||
// Test Description
|
||||
assert.NotEmpty(t, tool.Description())
|
||||
|
||||
// Test Options
|
||||
options := tool.Options()
|
||||
assert.NotEmpty(t, options)
|
||||
|
||||
// Test schema generation
|
||||
schema := GenerateReturnSchema(tool)
|
||||
assert.Contains(t, schema, "result")
|
||||
}
|
||||
```
|
||||
|
||||
#### 集成测试
|
||||
```go
|
||||
func TestToolNewFeatureIntegration(t *testing.T) {
|
||||
// Create mock request
|
||||
request := mcp.CallToolRequest{
|
||||
Params: mcp.CallToolRequestParams{
|
||||
Arguments: map[string]any{
|
||||
"param1": "value1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Execute tool
|
||||
tool := &ToolNewFeature{}
|
||||
handler := tool.Implement()
|
||||
result, err := handler(context.Background(), request)
|
||||
|
||||
// Verify result
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, result)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
### 最佳实践
|
||||
|
||||
## 📚 总结
|
||||
#### 工具设计
|
||||
- **单一职责**: 每个工具只负责一个特定功能
|
||||
- **清晰命名**: 使用描述性的工具名称
|
||||
- **完整文档**: 提供详细的描述和参数说明
|
||||
- **错误处理**: 提供有意义的错误消息
|
||||
|
||||
HttpRunner MCP Server 通过模块化的架构设计,将 UI 自动化功能按类别拆分为多个文件,每个文件专注于特定的功能领域。这种设计不仅提高了代码的可维护性和可扩展性,还使得开发者能够更容易地理解和贡献代码。
|
||||
#### 响应设计
|
||||
- **一致性**: 所有工具使用相同的响应格式
|
||||
- **信息丰富**: 返回足够的信息供客户端使用
|
||||
- **类型安全**: 使用适当的数据类型
|
||||
- **描述性**: 提供清晰的字段描述
|
||||
|
||||
### 核心优势
|
||||
#### 性能优化
|
||||
- **延迟加载**: 只在需要时初始化资源
|
||||
- **资源复用**: 复用驱动程序连接
|
||||
- **错误快速失败**: 尽早检测和报告错误
|
||||
- **日志记录**: 提供适当的日志级别
|
||||
|
||||
1. **模块化架构**: 按功能分类的文件组织,便于维护和扩展
|
||||
2. **统一接口**: 所有工具都实现相同的 ActionTool 接口
|
||||
3. **类型安全**: 强类型的参数处理和返回值定义
|
||||
4. **完整文档**: 每个工具都有详细的参数和返回值说明
|
||||
5. **易于测试**: 独立的工具实现便于单元测试
|
||||
## 📊 工具统计
|
||||
|
||||
该实现为 UI 自动化测试提供了一个完整、可扩展且高性能的 MCP 服务器解决方案。
|
||||
### 总计
|
||||
- **总工具数**: 40+ 个
|
||||
- **文件数**: 9 个工具文件
|
||||
- **支持平台**: Android、iOS、Web、Harmony OS
|
||||
|
||||
### 按类别分布
|
||||
- **设备管理**: 2 个工具
|
||||
- **触摸操作**: 5 个工具
|
||||
- **手势操作**: 7 个工具
|
||||
- **输入操作**: 2 个工具
|
||||
- **按键操作**: 3 个工具
|
||||
- **应用管理**: 6 个工具
|
||||
- **屏幕操作**: 3 个工具
|
||||
- **实用工具**: 4 个工具
|
||||
- **Web 操作**: 6 个工具
|
||||
- **AI 操作**: 3 个工具
|
||||
|
||||
## 🚀 性能特性
|
||||
|
||||
### 优化成果
|
||||
- **代码减少**: 相比原始实现减少约 70% 的样板代码
|
||||
- **一致性**: 100% 的工具使用统一响应格式
|
||||
- **自动化**: 完全自动化的模式生成
|
||||
- **类型安全**: 保持完整的类型安全性
|
||||
- **零手动定义**: 无需手动定义响应模式
|
||||
|
||||
### 架构优势
|
||||
- **极简化**: 单函数调用创建响应
|
||||
- **可维护性**: 清晰的代码结构和分离关注点
|
||||
- **开发体验**: 直观的 API 和最小认知开销
|
||||
- **自文档化**: 代码即文档的设计
|
||||
|
||||
## 📝 总结
|
||||
|
||||
HttpRunner MCP Server 提供了一个强大、灵活且易于使用的 UI 自动化平台。通过采用扁平化响应格式和自动化模式生成,实现了极简化的架构,同时保持了完整的功能性和类型安全性。
|
||||
|
||||
该架构的主要优势:
|
||||
- **统一性**: 所有工具遵循相同的模式
|
||||
- **简洁性**: 最小化的样板代码
|
||||
- **可扩展性**: 易于添加新功能
|
||||
- **可维护性**: 清晰的代码组织
|
||||
- **性能**: 优化的响应创建和处理
|
||||
|
||||
无论是进行移动应用测试、Web 自动化还是 AI 驱动的 UI 操作,HttpRunner MCP Server 都提供了必要的工具和基础设施来支持各种自动化需求。
|
||||
|
||||
@@ -3,6 +3,7 @@ package uixt
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/httprunner/httprunner/v5/internal/json"
|
||||
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -1562,3 +1563,84 @@ func TestPreMarkOperationConfiguration(t *testing.T) {
|
||||
_, exists := request2.Params.Arguments["pre_mark_operation"]
|
||||
assert.False(t, exists)
|
||||
}
|
||||
|
||||
func TestGenerateReturnSchema(t *testing.T) {
|
||||
// Test with ToolListPackages
|
||||
tool := ToolListPackages{}
|
||||
schema := GenerateReturnSchema(tool)
|
||||
|
||||
// Check that standard MCPResponse fields are included
|
||||
assert.Contains(t, schema, "action")
|
||||
assert.Contains(t, schema, "success")
|
||||
assert.Contains(t, schema, "message")
|
||||
assert.Equal(t, "string: Action performed", schema["action"])
|
||||
assert.Equal(t, "boolean: Whether the operation was successful", schema["success"])
|
||||
assert.Equal(t, "string: Human-readable message describing the result", schema["message"])
|
||||
|
||||
// Check that tool-specific fields are included at the same level
|
||||
assert.Contains(t, schema, "packages")
|
||||
assert.Contains(t, schema, "count")
|
||||
assert.Equal(t, "[]string: List of installed app package names on the device", schema["packages"])
|
||||
assert.Equal(t, "int: Number of installed packages", schema["count"])
|
||||
|
||||
// Ensure "data" field is not present in the new flat structure
|
||||
assert.NotContains(t, schema, "data")
|
||||
}
|
||||
|
||||
func TestMCPResponseInheritance(t *testing.T) {
|
||||
// Test creating a response with tool data
|
||||
returnData := ToolListPackages{
|
||||
Packages: []string{"com.example.app1", "com.example.app2"},
|
||||
Count: 2,
|
||||
}
|
||||
|
||||
// Test JSON marshaling
|
||||
jsonData, err := json.Marshal(returnData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Parse back to verify structure
|
||||
var parsed map[string]interface{}
|
||||
err = json.Unmarshal(jsonData, &parsed)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check that tool-specific fields are present
|
||||
assert.Equal(t, float64(2), parsed["count"]) // JSON numbers are float64
|
||||
|
||||
packages, ok := parsed["packages"].([]interface{})
|
||||
assert.True(t, ok)
|
||||
assert.Len(t, packages, 2)
|
||||
assert.Equal(t, "com.example.app1", packages[0])
|
||||
assert.Equal(t, "com.example.app2", packages[1])
|
||||
}
|
||||
|
||||
func TestNewMCPSuccessResponse(t *testing.T) {
|
||||
// Test the simplified NewMCPSuccessResponse function
|
||||
message := "Successfully slept for 5 seconds"
|
||||
returnData := ToolSleep{
|
||||
Seconds: 5.0,
|
||||
Duration: "5s",
|
||||
}
|
||||
|
||||
// Test JSON marshaling directly first
|
||||
jsonData, err := json.Marshal(returnData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Parse the JSON to verify structure
|
||||
var parsed map[string]interface{}
|
||||
err = json.Unmarshal(jsonData, &parsed)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, float64(5.0), parsed["seconds"])
|
||||
assert.Equal(t, "5s", parsed["duration"])
|
||||
|
||||
// Test the MCP response function with actual tool instance
|
||||
tool := &ToolSleep{}
|
||||
result := NewMCPSuccessResponse(message, tool)
|
||||
assert.NotNil(t, result)
|
||||
}
|
||||
|
||||
func TestNewMCPErrorResponse(t *testing.T) {
|
||||
// Test error response creation
|
||||
result := NewMCPErrorResponse("Test error message")
|
||||
assert.NotNil(t, result)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,10 @@ import (
|
||||
)
|
||||
|
||||
// ToolStartToGoal implements the start_to_goal tool call.
|
||||
type ToolStartToGoal struct{}
|
||||
type ToolStartToGoal struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
Prompt string `json:"prompt" desc:"Goal prompt that was executed"`
|
||||
}
|
||||
|
||||
func (t *ToolStartToGoal) Name() option.ActionName {
|
||||
return option.ACTION_StartToGoal
|
||||
@@ -41,10 +44,15 @@ func (t *ToolStartToGoal) Implement() server.ToolHandlerFunc {
|
||||
// Start to goal logic
|
||||
err = driverExt.StartToGoal(ctx, unifiedReq.Prompt)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Failed to achieve goal: %s", err.Error())), nil
|
||||
return NewMCPErrorResponse(fmt.Sprintf("Failed to achieve goal: %s", err.Error())), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Successfully achieved goal: %s", unifiedReq.Prompt)), nil
|
||||
message := fmt.Sprintf("Successfully achieved goal: %s", unifiedReq.Prompt)
|
||||
returnData := ToolStartToGoal{
|
||||
Prompt: unifiedReq.Prompt,
|
||||
}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,14 +70,11 @@ func (t *ToolStartToGoal) ConvertActionToCallToolRequest(action option.MobileAct
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid start to goal params: %v", action.Params)
|
||||
}
|
||||
|
||||
func (t *ToolStartToGoal) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"message": "string: Success message confirming goal was achieved, or error message if failed",
|
||||
}
|
||||
}
|
||||
|
||||
// ToolAIAction implements the ai_action tool call.
|
||||
type ToolAIAction struct{}
|
||||
type ToolAIAction struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
Prompt string `json:"prompt" desc:"AI action prompt that was executed"`
|
||||
}
|
||||
|
||||
func (t *ToolAIAction) Name() option.ActionName {
|
||||
return option.ACTION_AIAction
|
||||
@@ -99,10 +104,15 @@ func (t *ToolAIAction) Implement() server.ToolHandlerFunc {
|
||||
// AI action logic
|
||||
err = driverExt.AIAction(ctx, unifiedReq.Prompt)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("AI action failed: %s", err.Error())), nil
|
||||
return NewMCPErrorResponse(fmt.Sprintf("AI action failed: %s", err.Error())), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Successfully performed AI action with prompt: %s", unifiedReq.Prompt)), nil
|
||||
message := fmt.Sprintf("Successfully performed AI action with prompt: %s", unifiedReq.Prompt)
|
||||
returnData := ToolAIAction{
|
||||
Prompt: unifiedReq.Prompt,
|
||||
}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,14 +130,11 @@ func (t *ToolAIAction) ConvertActionToCallToolRequest(action option.MobileAction
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid AI action params: %v", action.Params)
|
||||
}
|
||||
|
||||
func (t *ToolAIAction) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"message": "string: Success message confirming AI action was performed, or error message if failed",
|
||||
}
|
||||
}
|
||||
|
||||
// ToolFinished implements the finished tool call.
|
||||
type ToolFinished struct{}
|
||||
type ToolFinished struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
Content string `json:"content" desc:"Task completion reason or result message"`
|
||||
}
|
||||
|
||||
func (t *ToolFinished) Name() option.ActionName {
|
||||
return option.ACTION_Finished
|
||||
@@ -150,7 +157,12 @@ func (t *ToolFinished) Implement() server.ToolHandlerFunc {
|
||||
}
|
||||
log.Info().Str("reason", unifiedReq.Content).Msg("task finished")
|
||||
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Task completed: %s", unifiedReq.Content)), nil
|
||||
message := fmt.Sprintf("Task completed: %s", unifiedReq.Content)
|
||||
returnData := ToolFinished{
|
||||
Content: unifiedReq.Content,
|
||||
}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,9 +175,3 @@ func (t *ToolFinished) ConvertActionToCallToolRequest(action option.MobileAction
|
||||
}
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid finished params: %v", action.Params)
|
||||
}
|
||||
|
||||
func (t *ToolFinished) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"message": "string: Success message confirming task completion, or error message if failed",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,11 @@ import (
|
||||
)
|
||||
|
||||
// ToolListPackages implements the list_packages tool call.
|
||||
type ToolListPackages struct{}
|
||||
type ToolListPackages struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
Packages []string `json:"packages" desc:"List of installed app package names on the device"`
|
||||
Count int `json:"count" desc:"Number of installed packages"`
|
||||
}
|
||||
|
||||
func (t *ToolListPackages) Name() option.ActionName {
|
||||
return option.ACTION_ListPackages
|
||||
@@ -35,9 +39,16 @@ func (t *ToolListPackages) Implement() server.ToolHandlerFunc {
|
||||
|
||||
apps, err := driverExt.IDriver.GetDevice().ListPackages()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return NewMCPErrorResponse("Failed to list packages: " + err.Error()), nil
|
||||
}
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Device packages: %v", apps)), nil
|
||||
|
||||
message := fmt.Sprintf("Found %d installed packages", len(apps))
|
||||
returnData := ToolListPackages{
|
||||
Packages: apps,
|
||||
Count: len(apps),
|
||||
}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,14 +56,11 @@ func (t *ToolListPackages) ConvertActionToCallToolRequest(action option.MobileAc
|
||||
return buildMCPCallToolRequest(t.Name(), map[string]any{}), nil
|
||||
}
|
||||
|
||||
func (t *ToolListPackages) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"packages": "[]string: List of installed app package names on the device",
|
||||
}
|
||||
}
|
||||
|
||||
// ToolLaunchApp implements the launch_app tool call.
|
||||
type ToolLaunchApp struct{}
|
||||
type ToolLaunchApp struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
PackageName string `json:"packageName" desc:"Package name of the launched app"`
|
||||
}
|
||||
|
||||
func (t *ToolLaunchApp) Name() option.ActionName {
|
||||
return option.ACTION_AppLaunch
|
||||
@@ -86,10 +94,13 @@ func (t *ToolLaunchApp) Implement() server.ToolHandlerFunc {
|
||||
// Launch app action logic
|
||||
err = driverExt.AppLaunch(unifiedReq.PackageName)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Launch app failed: %s", err.Error())), nil
|
||||
return NewMCPErrorResponse(fmt.Sprintf("Launch app failed: %s", err.Error())), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Successfully launched app: %s", unifiedReq.PackageName)), nil
|
||||
message := fmt.Sprintf("Successfully launched app: %s", unifiedReq.PackageName)
|
||||
returnData := ToolLaunchApp{PackageName: unifiedReq.PackageName}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,12 +114,12 @@ func (t *ToolLaunchApp) ConvertActionToCallToolRequest(action option.MobileActio
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid app launch params: %v", action.Params)
|
||||
}
|
||||
|
||||
func (t *ToolLaunchApp) ReturnSchema() map[string]string {
|
||||
return defaultReturnSchema()
|
||||
}
|
||||
|
||||
// ToolTerminateApp implements the terminate_app tool call.
|
||||
type ToolTerminateApp struct{}
|
||||
type ToolTerminateApp struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
PackageName string `json:"packageName" desc:"Package name of the terminated app"`
|
||||
WasRunning bool `json:"wasRunning" desc:"Whether the app was actually running before termination"`
|
||||
}
|
||||
|
||||
func (t *ToolTerminateApp) Name() option.ActionName {
|
||||
return option.ACTION_AppTerminate
|
||||
@@ -142,13 +153,19 @@ func (t *ToolTerminateApp) Implement() server.ToolHandlerFunc {
|
||||
// Terminate app action logic
|
||||
success, err := driverExt.AppTerminate(unifiedReq.PackageName)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Terminate app failed: %s", err.Error())), nil
|
||||
return NewMCPErrorResponse(fmt.Sprintf("Terminate app failed: %s", err.Error())), nil
|
||||
}
|
||||
if !success {
|
||||
log.Warn().Str("packageName", unifiedReq.PackageName).Msg("app was not running")
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Successfully terminated app: %s", unifiedReq.PackageName)), nil
|
||||
message := fmt.Sprintf("Successfully terminated app: %s", unifiedReq.PackageName)
|
||||
returnData := ToolTerminateApp{
|
||||
PackageName: unifiedReq.PackageName,
|
||||
WasRunning: success,
|
||||
}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,12 +179,11 @@ func (t *ToolTerminateApp) ConvertActionToCallToolRequest(action option.MobileAc
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid app terminate params: %v", action.Params)
|
||||
}
|
||||
|
||||
func (t *ToolTerminateApp) ReturnSchema() map[string]string {
|
||||
return defaultReturnSchema()
|
||||
}
|
||||
|
||||
// ToolAppInstall implements the app_install tool call.
|
||||
type ToolAppInstall struct{}
|
||||
type ToolAppInstall struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
Path string `json:"path" desc:"Path or URL of the installed app"`
|
||||
}
|
||||
|
||||
func (t *ToolAppInstall) Name() option.ActionName {
|
||||
return option.ACTION_AppInstall
|
||||
@@ -197,10 +213,13 @@ func (t *ToolAppInstall) Implement() server.ToolHandlerFunc {
|
||||
// App install action logic
|
||||
err = driverExt.GetDevice().Install(unifiedReq.AppUrl)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("App install failed: %s", err.Error())), nil
|
||||
return NewMCPErrorResponse(fmt.Sprintf("App install failed: %s", err.Error())), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Successfully installed app from: %s", unifiedReq.AppUrl)), nil
|
||||
message := fmt.Sprintf("Successfully installed app from: %s", unifiedReq.AppUrl)
|
||||
returnData := ToolAppInstall{Path: unifiedReq.AppUrl}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,15 +233,11 @@ func (t *ToolAppInstall) ConvertActionToCallToolRequest(action option.MobileActi
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid app install params: %v", action.Params)
|
||||
}
|
||||
|
||||
func (t *ToolAppInstall) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"message": "string: Success message confirming app installation",
|
||||
"appUrl": "string: URL or path of the app that was installed",
|
||||
}
|
||||
}
|
||||
|
||||
// ToolAppUninstall implements the app_uninstall tool call.
|
||||
type ToolAppUninstall struct{}
|
||||
type ToolAppUninstall struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
PackageName string `json:"packageName" desc:"Package name of the uninstalled app"`
|
||||
}
|
||||
|
||||
func (t *ToolAppUninstall) Name() option.ActionName {
|
||||
return option.ACTION_AppUninstall
|
||||
@@ -252,10 +267,13 @@ func (t *ToolAppUninstall) Implement() server.ToolHandlerFunc {
|
||||
// App uninstall action logic
|
||||
err = driverExt.GetDevice().Uninstall(unifiedReq.PackageName)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("App uninstall failed: %s", err.Error())), nil
|
||||
return NewMCPErrorResponse(fmt.Sprintf("App uninstall failed: %s", err.Error())), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Successfully uninstalled app: %s", unifiedReq.PackageName)), nil
|
||||
message := fmt.Sprintf("Successfully uninstalled app: %s", unifiedReq.PackageName)
|
||||
returnData := ToolAppUninstall{PackageName: unifiedReq.PackageName}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,15 +287,11 @@ func (t *ToolAppUninstall) ConvertActionToCallToolRequest(action option.MobileAc
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid app uninstall params: %v", action.Params)
|
||||
}
|
||||
|
||||
func (t *ToolAppUninstall) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"message": "string: Success message confirming app uninstallation",
|
||||
"packageName": "string: Package name of the app that was uninstalled",
|
||||
}
|
||||
}
|
||||
|
||||
// ToolAppClear implements the app_clear tool call.
|
||||
type ToolAppClear struct{}
|
||||
type ToolAppClear struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
PackageName string `json:"packageName" desc:"Package name of the app whose data was cleared"`
|
||||
}
|
||||
|
||||
func (t *ToolAppClear) Name() option.ActionName {
|
||||
return option.ACTION_AppClear
|
||||
@@ -307,10 +321,13 @@ func (t *ToolAppClear) Implement() server.ToolHandlerFunc {
|
||||
// App clear action logic
|
||||
err = driverExt.AppClear(unifiedReq.PackageName)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("App clear failed: %s", err.Error())), nil
|
||||
return NewMCPErrorResponse(fmt.Sprintf("App clear failed: %s", err.Error())), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Successfully cleared app: %s", unifiedReq.PackageName)), nil
|
||||
message := fmt.Sprintf("Successfully cleared app: %s", unifiedReq.PackageName)
|
||||
returnData := ToolAppClear{PackageName: unifiedReq.PackageName}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,10 +340,3 @@ func (t *ToolAppClear) ConvertActionToCallToolRequest(action option.MobileAction
|
||||
}
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid app clear params: %v", action.Params)
|
||||
}
|
||||
|
||||
func (t *ToolAppClear) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"message": "string: Success message confirming app data and cache were cleared",
|
||||
"packageName": "string: Package name of the app that was cleared",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,10 @@ import (
|
||||
)
|
||||
|
||||
// ToolPressButton implements the press_button tool call.
|
||||
type ToolPressButton struct{}
|
||||
type ToolPressButton struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
Button string `json:"button" desc:"Name of the button that was pressed"`
|
||||
}
|
||||
|
||||
func (t *ToolPressButton) Name() option.ActionName {
|
||||
return option.ACTION_PressButton
|
||||
@@ -41,10 +44,13 @@ func (t *ToolPressButton) Implement() server.ToolHandlerFunc {
|
||||
// Press button action logic
|
||||
err = driverExt.PressButton(types.DeviceButton(unifiedReq.Button))
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Press button failed: %s", err.Error())), nil
|
||||
return NewMCPErrorResponse(fmt.Sprintf("Press button failed: %s", err.Error())), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Successfully pressed button: %s", unifiedReq.Button)), nil
|
||||
message := fmt.Sprintf("Successfully pressed button: %s", unifiedReq.Button)
|
||||
returnData := ToolPressButton{Button: string(unifiedReq.Button)}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,15 +64,9 @@ func (t *ToolPressButton) ConvertActionToCallToolRequest(action option.MobileAct
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid press button params: %v", action.Params)
|
||||
}
|
||||
|
||||
func (t *ToolPressButton) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"message": "string: Success message confirming the button press operation",
|
||||
"button": "string: Name of the button that was pressed",
|
||||
}
|
||||
}
|
||||
|
||||
// ToolHome implements the home tool call.
|
||||
type ToolHome struct{}
|
||||
type ToolHome struct { // Return data fields - these define the structure of data returned by this tool
|
||||
}
|
||||
|
||||
func (t *ToolHome) Name() option.ActionName {
|
||||
return option.ACTION_Home
|
||||
@@ -91,10 +91,13 @@ func (t *ToolHome) Implement() server.ToolHandlerFunc {
|
||||
// Home action logic
|
||||
err = driverExt.Home()
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Home button press failed: %s", err.Error())), nil
|
||||
return NewMCPErrorResponse(fmt.Sprintf("Home button press failed: %s", err.Error())), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText("Successfully pressed home button"), nil
|
||||
message := "Successfully pressed home button"
|
||||
returnData := ToolHome{}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,14 +105,9 @@ func (t *ToolHome) ConvertActionToCallToolRequest(action option.MobileAction) (m
|
||||
return buildMCPCallToolRequest(t.Name(), map[string]any{}), nil
|
||||
}
|
||||
|
||||
func (t *ToolHome) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"message": "string: Success message confirming home button was pressed",
|
||||
}
|
||||
}
|
||||
|
||||
// ToolBack implements the back tool call.
|
||||
type ToolBack struct{}
|
||||
type ToolBack struct { // Return data fields - these define the structure of data returned by this tool
|
||||
}
|
||||
|
||||
func (t *ToolBack) Name() option.ActionName {
|
||||
return option.ACTION_Back
|
||||
@@ -134,19 +132,16 @@ func (t *ToolBack) Implement() server.ToolHandlerFunc {
|
||||
// Back action logic
|
||||
err = driverExt.Back()
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Back button press failed: %s", err.Error())), nil
|
||||
return NewMCPErrorResponse(fmt.Sprintf("Back button press failed: %s", err.Error())), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText("Successfully pressed back button"), nil
|
||||
message := "Successfully pressed back button"
|
||||
returnData := ToolBack{}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ToolBack) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) {
|
||||
return buildMCPCallToolRequest(t.Name(), map[string]any{}), nil
|
||||
}
|
||||
|
||||
func (t *ToolBack) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"message": "string: Success message confirming back button was pressed",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/danielpaulus/go-ios/ios"
|
||||
"github.com/httprunner/httprunner/v5/internal/json"
|
||||
"github.com/httprunner/httprunner/v5/pkg/gadb"
|
||||
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
@@ -14,7 +13,14 @@ import (
|
||||
)
|
||||
|
||||
// ToolListAvailableDevices implements the list_available_devices tool call.
|
||||
type ToolListAvailableDevices struct{}
|
||||
type ToolListAvailableDevices struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
AndroidDevices []string `json:"androidDevices" desc:"List of Android device serial numbers"`
|
||||
IosDevices []string `json:"iosDevices" desc:"List of iOS device UDIDs"`
|
||||
TotalCount int `json:"totalCount" desc:"Total number of available devices"`
|
||||
AndroidCount int `json:"androidCount" desc:"Number of Android devices"`
|
||||
IosCount int `json:"iosCount" desc:"Number of iOS devices"`
|
||||
}
|
||||
|
||||
func (t *ToolListAvailableDevices) Name() option.ActionName {
|
||||
return option.ACTION_ListAvailableDevices
|
||||
@@ -59,8 +65,19 @@ func (t *ToolListAvailableDevices) Implement() server.ToolHandlerFunc {
|
||||
deviceList["iosDevices"] = serialList
|
||||
}
|
||||
|
||||
jsonResult, _ := json.Marshal(deviceList)
|
||||
return mcp.NewToolResultText(string(jsonResult)), nil
|
||||
// Create structured response
|
||||
totalDevices := len(deviceList["androidDevices"]) + len(deviceList["iosDevices"])
|
||||
message := fmt.Sprintf("Found %d available devices (%d Android, %d iOS)",
|
||||
totalDevices, len(deviceList["androidDevices"]), len(deviceList["iosDevices"]))
|
||||
returnData := ToolListAvailableDevices{
|
||||
AndroidDevices: deviceList["androidDevices"],
|
||||
IosDevices: deviceList["iosDevices"],
|
||||
TotalCount: totalDevices,
|
||||
AndroidCount: len(deviceList["androidDevices"]),
|
||||
IosCount: len(deviceList["iosDevices"]),
|
||||
}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,15 +85,11 @@ func (t *ToolListAvailableDevices) ConvertActionToCallToolRequest(action option.
|
||||
return buildMCPCallToolRequest(t.Name(), map[string]any{}), nil
|
||||
}
|
||||
|
||||
func (t *ToolListAvailableDevices) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"androidDevices": "[]string: List of Android device serial numbers",
|
||||
"iosDevices": "[]string: List of iOS device UDIDs",
|
||||
}
|
||||
}
|
||||
|
||||
// ToolSelectDevice implements the select_device tool call.
|
||||
type ToolSelectDevice struct{}
|
||||
type ToolSelectDevice struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
DeviceUUID string `json:"deviceUUID" desc:"UUID of the selected device"`
|
||||
}
|
||||
|
||||
func (t *ToolSelectDevice) Name() option.ActionName {
|
||||
return option.ACTION_SelectDevice
|
||||
@@ -101,16 +114,13 @@ func (t *ToolSelectDevice) Implement() server.ToolHandlerFunc {
|
||||
}
|
||||
|
||||
uuid := driverExt.IDriver.GetDevice().UUID()
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Selected device: %s", uuid)), nil
|
||||
message := fmt.Sprintf("Selected device: %s", uuid)
|
||||
returnData := ToolSelectDevice{DeviceUUID: uuid}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ToolSelectDevice) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) {
|
||||
return buildMCPCallToolRequest(t.Name(), map[string]any{}), nil
|
||||
}
|
||||
|
||||
func (t *ToolSelectDevice) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"message": "string: Success message with selected device UUID",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,10 @@ import (
|
||||
)
|
||||
|
||||
// ToolInput implements the input tool call.
|
||||
type ToolInput struct{}
|
||||
type ToolInput struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
Text string `json:"text" desc:"Text that was input"`
|
||||
}
|
||||
|
||||
func (t *ToolInput) Name() option.ActionName {
|
||||
return option.ACTION_Input
|
||||
@@ -44,10 +47,13 @@ func (t *ToolInput) Implement() server.ToolHandlerFunc {
|
||||
// Input action logic
|
||||
err = driverExt.Input(unifiedReq.Text)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Input failed: %s", err.Error())), nil
|
||||
return NewMCPErrorResponse(fmt.Sprintf("Input failed: %s", err.Error())), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Successfully input text: %s", unifiedReq.Text)), nil
|
||||
message := fmt.Sprintf("Successfully input text: %s", unifiedReq.Text)
|
||||
returnData := ToolInput{Text: unifiedReq.Text}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,15 +65,11 @@ func (t *ToolInput) ConvertActionToCallToolRequest(action option.MobileAction) (
|
||||
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
||||
}
|
||||
|
||||
func (t *ToolInput) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"message": "string: Success message confirming text was input",
|
||||
"text": "string: Text content that was input into the field",
|
||||
}
|
||||
}
|
||||
|
||||
// ToolSetIme implements the set_ime tool call.
|
||||
type ToolSetIme struct{}
|
||||
type ToolSetIme struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
Ime string `json:"ime" desc:"IME that was set"`
|
||||
}
|
||||
|
||||
func (t *ToolSetIme) Name() option.ActionName {
|
||||
return option.ACTION_SetIme
|
||||
@@ -97,10 +99,13 @@ func (t *ToolSetIme) Implement() server.ToolHandlerFunc {
|
||||
// Set IME action logic
|
||||
err = driverExt.SetIme(unifiedReq.Ime)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Set IME failed: %s", err.Error())), nil
|
||||
return NewMCPErrorResponse(fmt.Sprintf("Set IME failed: %s", err.Error())), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Successfully set IME to: %s", unifiedReq.Ime)), nil
|
||||
message := fmt.Sprintf("Successfully set IME to: %s", unifiedReq.Ime)
|
||||
returnData := ToolSetIme{Ime: unifiedReq.Ime}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,10 +118,3 @@ func (t *ToolSetIme) ConvertActionToCallToolRequest(action option.MobileAction)
|
||||
}
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid set ime params: %v", action.Params)
|
||||
}
|
||||
|
||||
func (t *ToolSetIme) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"message": "string: Success message confirming IME was set",
|
||||
"ime": "string: Input method editor that was set",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@ import (
|
||||
)
|
||||
|
||||
// ToolScreenShot implements the screenshot tool call.
|
||||
type ToolScreenShot struct{}
|
||||
type ToolScreenShot struct { // Return data fields - these define the structure of data returned by this tool
|
||||
// Note: This tool returns image data, not JSON, so no additional fields needed
|
||||
}
|
||||
|
||||
func (t *ToolScreenShot) Name() option.ActionName {
|
||||
return option.ACTION_ScreenShot
|
||||
@@ -47,16 +49,12 @@ func (t *ToolScreenShot) ConvertActionToCallToolRequest(action option.MobileActi
|
||||
return buildMCPCallToolRequest(t.Name(), map[string]any{}), nil
|
||||
}
|
||||
|
||||
func (t *ToolScreenShot) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"image": "string: Base64 encoded screenshot image in JPEG format",
|
||||
"name": "string: Image name identifier (typically 'screenshot')",
|
||||
"type": "string: MIME type of the image (image/jpeg)",
|
||||
}
|
||||
}
|
||||
|
||||
// ToolGetScreenSize implements the get_screen_size tool call.
|
||||
type ToolGetScreenSize struct{}
|
||||
type ToolGetScreenSize struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
Width int `json:"width" desc:"Screen width in pixels"`
|
||||
Height int `json:"height" desc:"Screen height in pixels"`
|
||||
}
|
||||
|
||||
func (t *ToolGetScreenSize) Name() option.ActionName {
|
||||
return option.ACTION_GetScreenSize
|
||||
@@ -80,11 +78,16 @@ func (t *ToolGetScreenSize) Implement() server.ToolHandlerFunc {
|
||||
|
||||
screenSize, err := driverExt.IDriver.WindowSize()
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError("Get screen size failed: " + err.Error()), nil
|
||||
return NewMCPErrorResponse("Get screen size failed: " + err.Error()), nil
|
||||
}
|
||||
return mcp.NewToolResultText(
|
||||
fmt.Sprintf("Screen size: %d x %d pixels", screenSize.Width, screenSize.Height),
|
||||
), nil
|
||||
|
||||
message := fmt.Sprintf("Screen size: %d x %d pixels", screenSize.Width, screenSize.Height)
|
||||
returnData := ToolGetScreenSize{
|
||||
Width: screenSize.Width,
|
||||
Height: screenSize.Height,
|
||||
}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,16 +95,12 @@ func (t *ToolGetScreenSize) ConvertActionToCallToolRequest(action option.MobileA
|
||||
return buildMCPCallToolRequest(t.Name(), map[string]any{}), nil
|
||||
}
|
||||
|
||||
func (t *ToolGetScreenSize) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"width": "int: Screen width in pixels",
|
||||
"height": "int: Screen height in pixels",
|
||||
"message": "string: Formatted message with screen dimensions",
|
||||
}
|
||||
}
|
||||
|
||||
// ToolGetSource implements the get_source tool call.
|
||||
type ToolGetSource struct{}
|
||||
type ToolGetSource struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
PackageName string `json:"packageName" desc:"Package name of the app whose source was retrieved"`
|
||||
Source string `json:"source" desc:"UI hierarchy/source tree data in XML or JSON format"`
|
||||
}
|
||||
|
||||
func (t *ToolGetSource) Name() option.ActionName {
|
||||
return option.ACTION_GetSource
|
||||
@@ -129,12 +128,18 @@ func (t *ToolGetSource) Implement() server.ToolHandlerFunc {
|
||||
}
|
||||
|
||||
// Get source action logic
|
||||
_, err = driverExt.Source(option.WithProcessName(unifiedReq.PackageName))
|
||||
sourceData, err := driverExt.Source(option.WithProcessName(unifiedReq.PackageName))
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Get source failed: %s", err.Error())), nil
|
||||
return NewMCPErrorResponse(fmt.Sprintf("Get source failed: %s", err.Error())), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Successfully retrieved source for package: %s", unifiedReq.PackageName)), nil
|
||||
message := fmt.Sprintf("Successfully retrieved source for package: %s", unifiedReq.PackageName)
|
||||
returnData := ToolGetSource{
|
||||
PackageName: unifiedReq.PackageName,
|
||||
Source: sourceData,
|
||||
}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,11 +152,3 @@ func (t *ToolGetSource) ConvertActionToCallToolRequest(action option.MobileActio
|
||||
}
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid get source params: %v", action.Params)
|
||||
}
|
||||
|
||||
func (t *ToolGetSource) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"message": "string: Success message confirming UI source was retrieved",
|
||||
"packageName": "string: Package name of the app whose source was retrieved",
|
||||
"source": "string: UI hierarchy/source tree data in XML or JSON format",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,10 @@ import (
|
||||
// ToolSwipe implements the generic swipe tool call.
|
||||
// It automatically determines whether to use direction-based or coordinate-based swipe
|
||||
// based on the params type.
|
||||
type ToolSwipe struct{}
|
||||
type ToolSwipe struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
SwipeType string `json:"swipeType" desc:"Type of swipe performed (direction or coordinate)"`
|
||||
}
|
||||
|
||||
func (t *ToolSwipe) Name() option.ActionName {
|
||||
return option.ACTION_Swipe
|
||||
@@ -75,19 +78,15 @@ func (t *ToolSwipe) ConvertActionToCallToolRequest(action option.MobileAction) (
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid swipe params: %v, expected string direction or [fromX, fromY, toX, toY] coordinates", action.Params)
|
||||
}
|
||||
|
||||
func (t *ToolSwipe) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"message": "string: Success message confirming the swipe operation",
|
||||
"direction": "string: Direction of swipe (for directional swipes)",
|
||||
"fromX": "float64: Starting X coordinate (for coordinate-based swipes)",
|
||||
"fromY": "float64: Starting Y coordinate (for coordinate-based swipes)",
|
||||
"toX": "float64: Ending X coordinate (for coordinate-based swipes)",
|
||||
"toY": "float64: Ending Y coordinate (for coordinate-based swipes)",
|
||||
}
|
||||
}
|
||||
|
||||
// ToolSwipeDirection implements the swipe_direction tool call.
|
||||
type ToolSwipeDirection struct{}
|
||||
type ToolSwipeDirection struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
Direction string `json:"direction" desc:"Direction that was swiped (up/down/left/right)"`
|
||||
FromX float64 `json:"fromX" desc:"Starting X coordinate of the swipe"`
|
||||
FromY float64 `json:"fromY" desc:"Starting Y coordinate of the swipe"`
|
||||
ToX float64 `json:"toX" desc:"Ending X coordinate of the swipe"`
|
||||
ToY float64 `json:"toY" desc:"Ending Y coordinate of the swipe"`
|
||||
}
|
||||
|
||||
func (t *ToolSwipeDirection) Name() option.ActionName {
|
||||
return option.ACTION_SwipeDirection
|
||||
@@ -137,25 +136,38 @@ func (t *ToolSwipeDirection) Implement() server.ToolHandlerFunc {
|
||||
}
|
||||
|
||||
// Convert direction to coordinates and perform swipe
|
||||
var fromX, fromY, toX, toY float64
|
||||
switch swipeDirection {
|
||||
case "up":
|
||||
err = driverExt.Swipe(0.5, 0.5, 0.5, 0.1, opts...)
|
||||
fromX, fromY, toX, toY = 0.5, 0.5, 0.5, 0.1
|
||||
err = driverExt.Swipe(fromX, fromY, toX, toY, opts...)
|
||||
case "down":
|
||||
err = driverExt.Swipe(0.5, 0.5, 0.5, 0.9, opts...)
|
||||
fromX, fromY, toX, toY = 0.5, 0.5, 0.5, 0.9
|
||||
err = driverExt.Swipe(fromX, fromY, toX, toY, opts...)
|
||||
case "left":
|
||||
err = driverExt.Swipe(0.5, 0.5, 0.1, 0.5, opts...)
|
||||
fromX, fromY, toX, toY = 0.5, 0.5, 0.1, 0.5
|
||||
err = driverExt.Swipe(fromX, fromY, toX, toY, opts...)
|
||||
case "right":
|
||||
err = driverExt.Swipe(0.5, 0.5, 0.9, 0.5, opts...)
|
||||
fromX, fromY, toX, toY = 0.5, 0.5, 0.9, 0.5
|
||||
err = driverExt.Swipe(fromX, fromY, toX, toY, opts...)
|
||||
default:
|
||||
return mcp.NewToolResultError(
|
||||
fmt.Sprintf("Unexpected swipe direction: %s", swipeDirection)), nil
|
||||
return NewMCPErrorResponse(fmt.Sprintf("Unexpected swipe direction: %s", swipeDirection)), nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Swipe failed: %s", err.Error())), nil
|
||||
return NewMCPErrorResponse(fmt.Sprintf("Swipe failed: %s", err.Error())), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Successfully swiped %s", swipeDirection)), nil
|
||||
message := fmt.Sprintf("Successfully swiped %s", swipeDirection)
|
||||
returnData := ToolSwipeDirection{
|
||||
Direction: swipeDirection,
|
||||
FromX: fromX,
|
||||
FromY: fromY,
|
||||
ToX: toX,
|
||||
ToY: toY,
|
||||
}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,15 +193,14 @@ func (t *ToolSwipeDirection) ConvertActionToCallToolRequest(action option.Mobile
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid swipe params: %v", action.Params)
|
||||
}
|
||||
|
||||
func (t *ToolSwipeDirection) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"message": "string: Success message confirming the directional swipe",
|
||||
"direction": "string: Direction that was swiped (up/down/left/right)",
|
||||
}
|
||||
}
|
||||
|
||||
// ToolSwipeCoordinate implements the swipe_coordinate tool call.
|
||||
type ToolSwipeCoordinate struct{}
|
||||
type ToolSwipeCoordinate struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
FromX float64 `json:"fromX" desc:"Starting X coordinate of the swipe"`
|
||||
FromY float64 `json:"fromY" desc:"Starting Y coordinate of the swipe"`
|
||||
ToX float64 `json:"toX" desc:"Ending X coordinate of the swipe"`
|
||||
ToY float64 `json:"toY" desc:"Ending Y coordinate of the swipe"`
|
||||
}
|
||||
|
||||
func (t *ToolSwipeCoordinate) Name() option.ActionName {
|
||||
return option.ACTION_SwipeCoordinate
|
||||
@@ -244,11 +255,19 @@ func (t *ToolSwipeCoordinate) Implement() server.ToolHandlerFunc {
|
||||
swipeAction := prepareSwipeAction(driverExt, params, opts...)
|
||||
err = swipeAction(driverExt)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Advanced swipe failed: %s", err.Error())), nil
|
||||
return NewMCPErrorResponse(fmt.Sprintf("Advanced swipe failed: %s", err.Error())), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Successfully performed advanced swipe from (%.2f, %.2f) to (%.2f, %.2f)",
|
||||
unifiedReq.FromX, unifiedReq.FromY, unifiedReq.ToX, unifiedReq.ToY)), nil
|
||||
message := fmt.Sprintf("Successfully performed advanced swipe from (%.2f, %.2f) to (%.2f, %.2f)",
|
||||
unifiedReq.FromX, unifiedReq.FromY, unifiedReq.ToX, unifiedReq.ToY)
|
||||
returnData := ToolSwipeCoordinate{
|
||||
FromX: unifiedReq.FromX,
|
||||
FromY: unifiedReq.FromY,
|
||||
ToX: unifiedReq.ToX,
|
||||
ToY: unifiedReq.ToY,
|
||||
}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,18 +295,11 @@ func (t *ToolSwipeCoordinate) ConvertActionToCallToolRequest(action option.Mobil
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid swipe advanced params: %v", action.Params)
|
||||
}
|
||||
|
||||
func (t *ToolSwipeCoordinate) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"message": "string: Success message confirming the coordinate-based swipe",
|
||||
"fromX": "float64: Starting X coordinate of the swipe",
|
||||
"fromY": "float64: Starting Y coordinate of the swipe",
|
||||
"toX": "float64: Ending X coordinate of the swipe",
|
||||
"toY": "float64: Ending Y coordinate of the swipe",
|
||||
}
|
||||
}
|
||||
|
||||
// ToolSwipeToTapApp implements the swipe_to_tap_app tool call.
|
||||
type ToolSwipeToTapApp struct{}
|
||||
type ToolSwipeToTapApp struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
AppName string `json:"appName" desc:"Name of the app that was found and tapped"`
|
||||
}
|
||||
|
||||
func (t *ToolSwipeToTapApp) Name() option.ActionName {
|
||||
return option.ACTION_SwipeToTapApp
|
||||
@@ -333,10 +345,13 @@ func (t *ToolSwipeToTapApp) Implement() server.ToolHandlerFunc {
|
||||
// Swipe to tap app action logic
|
||||
err = driverExt.SwipeToTapApp(unifiedReq.AppName, opts...)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Swipe to tap app failed: %s", err.Error())), nil
|
||||
return NewMCPErrorResponse(fmt.Sprintf("Swipe to tap app failed: %s", err.Error())), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Successfully found and tapped app: %s", unifiedReq.AppName)), nil
|
||||
message := fmt.Sprintf("Successfully found and tapped app: %s", unifiedReq.AppName)
|
||||
returnData := ToolSwipeToTapApp{AppName: unifiedReq.AppName}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,15 +369,11 @@ func (t *ToolSwipeToTapApp) ConvertActionToCallToolRequest(action option.MobileA
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid swipe to tap app params: %v", action.Params)
|
||||
}
|
||||
|
||||
func (t *ToolSwipeToTapApp) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"message": "string: Success message confirming the app was found and tapped",
|
||||
"appName": "string: Name of the app that was found and tapped",
|
||||
}
|
||||
}
|
||||
|
||||
// ToolSwipeToTapText implements the swipe_to_tap_text tool call.
|
||||
type ToolSwipeToTapText struct{}
|
||||
type ToolSwipeToTapText struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
Text string `json:"text" desc:"Text that was found and tapped"`
|
||||
}
|
||||
|
||||
func (t *ToolSwipeToTapText) Name() option.ActionName {
|
||||
return option.ACTION_SwipeToTapText
|
||||
@@ -411,10 +422,13 @@ func (t *ToolSwipeToTapText) Implement() server.ToolHandlerFunc {
|
||||
// Swipe to tap text action logic
|
||||
err = driverExt.SwipeToTapTexts([]string{unifiedReq.Text}, opts...)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Swipe to tap text failed: %s", err.Error())), nil
|
||||
return NewMCPErrorResponse(fmt.Sprintf("Swipe to tap text failed: %s", err.Error())), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Successfully found and tapped text: %s", unifiedReq.Text)), nil
|
||||
message := fmt.Sprintf("Successfully found and tapped text: %s", unifiedReq.Text)
|
||||
returnData := ToolSwipeToTapText{Text: unifiedReq.Text}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -432,15 +446,12 @@ func (t *ToolSwipeToTapText) ConvertActionToCallToolRequest(action option.Mobile
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid swipe to tap text params: %v", action.Params)
|
||||
}
|
||||
|
||||
func (t *ToolSwipeToTapText) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"message": "string: Success message confirming the text was found and tapped",
|
||||
"text": "string: Text content that was found and tapped",
|
||||
}
|
||||
}
|
||||
|
||||
// ToolSwipeToTapTexts implements the swipe_to_tap_texts tool call.
|
||||
type ToolSwipeToTapTexts struct{}
|
||||
type ToolSwipeToTapTexts struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
Texts []string `json:"texts" desc:"List of texts that were searched for"`
|
||||
TappedText string `json:"tappedText" desc:"The specific text that was found and tapped"`
|
||||
}
|
||||
|
||||
func (t *ToolSwipeToTapTexts) Name() option.ActionName {
|
||||
return option.ACTION_SwipeToTapTexts
|
||||
@@ -490,10 +501,16 @@ func (t *ToolSwipeToTapTexts) Implement() server.ToolHandlerFunc {
|
||||
log.Info().Strs("texts", unifiedReq.Texts).Msg("swipe to tap texts")
|
||||
err = driverExt.SwipeToTapTexts(unifiedReq.Texts, opts...)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Swipe to tap texts failed: %s", err.Error())), nil
|
||||
return NewMCPErrorResponse(fmt.Sprintf("Swipe to tap texts failed: %s", err.Error())), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Successfully found and tapped one of texts: %v", unifiedReq.Texts)), nil
|
||||
message := fmt.Sprintf("Successfully found and tapped one of texts: %v", unifiedReq.Texts)
|
||||
returnData := ToolSwipeToTapTexts{
|
||||
Texts: unifiedReq.Texts,
|
||||
TappedText: "unknown", // We don't know which specific text was tapped
|
||||
}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -516,16 +533,14 @@ func (t *ToolSwipeToTapTexts) ConvertActionToCallToolRequest(action option.Mobil
|
||||
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
||||
}
|
||||
|
||||
func (t *ToolSwipeToTapTexts) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"message": "string: Success message confirming one of the texts was found and tapped",
|
||||
"texts": "[]string: List of text options that were searched for",
|
||||
"foundText": "string: The specific text that was actually found and tapped",
|
||||
}
|
||||
}
|
||||
|
||||
// ToolDrag implements the drag tool call.
|
||||
type ToolDrag struct{}
|
||||
type ToolDrag struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
FromX float64 `json:"fromX" desc:"Starting X coordinate of the drag"`
|
||||
FromY float64 `json:"fromY" desc:"Starting Y coordinate of the drag"`
|
||||
ToX float64 `json:"toX" desc:"Ending X coordinate of the drag"`
|
||||
ToY float64 `json:"toY" desc:"Ending Y coordinate of the drag"`
|
||||
}
|
||||
|
||||
func (t *ToolDrag) Name() option.ActionName {
|
||||
return option.ACTION_Drag
|
||||
@@ -577,11 +592,19 @@ func (t *ToolDrag) Implement() server.ToolHandlerFunc {
|
||||
|
||||
err = driverExt.Swipe(unifiedReq.FromX, unifiedReq.FromY, unifiedReq.ToX, unifiedReq.ToY, opts...)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Drag failed: %s", err.Error())), nil
|
||||
return NewMCPErrorResponse(fmt.Sprintf("Drag failed: %s", err.Error())), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Successfully dragged from (%.2f, %.2f) to (%.2f, %.2f)",
|
||||
unifiedReq.FromX, unifiedReq.FromY, unifiedReq.ToX, unifiedReq.ToY)), nil
|
||||
message := fmt.Sprintf("Successfully dragged from (%.2f, %.2f) to (%.2f, %.2f)",
|
||||
unifiedReq.FromX, unifiedReq.FromY, unifiedReq.ToX, unifiedReq.ToY)
|
||||
returnData := ToolDrag{
|
||||
FromX: unifiedReq.FromX,
|
||||
FromY: unifiedReq.FromY,
|
||||
ToX: unifiedReq.ToX,
|
||||
ToY: unifiedReq.ToY,
|
||||
}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -605,13 +628,3 @@ func (t *ToolDrag) ConvertActionToCallToolRequest(action option.MobileAction) (m
|
||||
}
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid drag parameters: %v", action.Params)
|
||||
}
|
||||
|
||||
func (t *ToolDrag) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"message": "string: Success message confirming the drag operation",
|
||||
"fromX": "float64: Starting X coordinate of the drag",
|
||||
"fromY": "float64: Starting Y coordinate of the drag",
|
||||
"toX": "float64: Ending X coordinate of the drag",
|
||||
"toY": "float64: Ending Y coordinate of the drag",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,11 @@ import (
|
||||
)
|
||||
|
||||
// ToolTapXY implements the tap_xy tool call.
|
||||
type ToolTapXY struct{}
|
||||
type ToolTapXY struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
X float64 `json:"x" desc:"X coordinate where tap was performed"`
|
||||
Y float64 `json:"y" desc:"Y coordinate where tap was performed"`
|
||||
}
|
||||
|
||||
func (t *ToolTapXY) Name() option.ActionName {
|
||||
return option.ACTION_TapXY
|
||||
@@ -54,10 +58,16 @@ func (t *ToolTapXY) Implement() server.ToolHandlerFunc {
|
||||
// Tap action logic
|
||||
err = driverExt.TapXY(unifiedReq.X, unifiedReq.Y, opts...)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Tap failed: %s", err.Error())), nil
|
||||
return NewMCPErrorResponse(fmt.Sprintf("Tap failed: %s", err.Error())), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Successfully tapped at coordinates (%.2f, %.2f)", unifiedReq.X, unifiedReq.Y)), nil
|
||||
message := fmt.Sprintf("Successfully tapped at coordinates (%.2f, %.2f)", unifiedReq.X, unifiedReq.Y)
|
||||
returnData := ToolTapXY{
|
||||
X: unifiedReq.X,
|
||||
Y: unifiedReq.Y,
|
||||
}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,14 +91,12 @@ func (t *ToolTapXY) ConvertActionToCallToolRequest(action option.MobileAction) (
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid tap params: %v", action.Params)
|
||||
}
|
||||
|
||||
func (t *ToolTapXY) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"message": "string: Success message confirming tap operation at specified coordinates",
|
||||
}
|
||||
}
|
||||
|
||||
// ToolTapAbsXY implements the tap_abs_xy tool call.
|
||||
type ToolTapAbsXY struct{}
|
||||
type ToolTapAbsXY struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
X float64 `json:"x" desc:"X coordinate where tap was performed (absolute pixels)"`
|
||||
Y float64 `json:"y" desc:"Y coordinate where tap was performed (absolute pixels)"`
|
||||
}
|
||||
|
||||
func (t *ToolTapAbsXY) Name() option.ActionName {
|
||||
return option.ACTION_TapAbsXY
|
||||
@@ -136,10 +144,16 @@ func (t *ToolTapAbsXY) Implement() server.ToolHandlerFunc {
|
||||
// Tap absolute XY action logic
|
||||
err = driverExt.TapAbsXY(unifiedReq.X, unifiedReq.Y, opts...)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Tap absolute XY failed: %s", err.Error())), nil
|
||||
return NewMCPErrorResponse(fmt.Sprintf("Tap absolute XY failed: %s", err.Error())), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Successfully tapped at absolute coordinates (%.0f, %.0f)", unifiedReq.X, unifiedReq.Y)), nil
|
||||
message := fmt.Sprintf("Successfully tapped at absolute coordinates (%.0f, %.0f)", unifiedReq.X, unifiedReq.Y)
|
||||
returnData := ToolTapAbsXY{
|
||||
X: unifiedReq.X,
|
||||
Y: unifiedReq.Y,
|
||||
}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,21 +177,11 @@ func (t *ToolTapAbsXY) ConvertActionToCallToolRequest(action option.MobileAction
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid tap abs params: %v", action.Params)
|
||||
}
|
||||
|
||||
func (t *ToolTapAbsXY) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"message": "string: Success message confirming tap operation at absolute coordinates",
|
||||
}
|
||||
}
|
||||
|
||||
// defaultReturnSchema provides a standard return schema for most tools
|
||||
func defaultReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"message": "string: Success message confirming the operation was completed",
|
||||
}
|
||||
}
|
||||
|
||||
// ToolTapByOCR implements the tap_ocr tool call.
|
||||
type ToolTapByOCR struct{}
|
||||
type ToolTapByOCR struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
Text string `json:"text" desc:"Text that was tapped by OCR"`
|
||||
}
|
||||
|
||||
func (t *ToolTapByOCR) Name() option.ActionName {
|
||||
return option.ACTION_TapByOCR
|
||||
@@ -220,10 +224,13 @@ func (t *ToolTapByOCR) Implement() server.ToolHandlerFunc {
|
||||
// Tap by OCR action logic
|
||||
err = driverExt.TapByOCR(unifiedReq.Text, opts...)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Tap by OCR failed: %s", err.Error())), nil
|
||||
return NewMCPErrorResponse(fmt.Sprintf("Tap by OCR failed: %s", err.Error())), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Successfully tapped on OCR text: %s", unifiedReq.Text)), nil
|
||||
message := fmt.Sprintf("Successfully tapped on OCR text: %s", unifiedReq.Text)
|
||||
returnData := ToolTapByOCR{Text: unifiedReq.Text}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,14 +248,9 @@ func (t *ToolTapByOCR) ConvertActionToCallToolRequest(action option.MobileAction
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid tap by OCR params: %v", action.Params)
|
||||
}
|
||||
|
||||
func (t *ToolTapByOCR) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"message": "string: Success message confirming the operation was completed",
|
||||
}
|
||||
}
|
||||
|
||||
// ToolTapByCV implements the tap_cv tool call.
|
||||
type ToolTapByCV struct{}
|
||||
type ToolTapByCV struct { // Return data fields - these define the structure of data returned by this tool
|
||||
}
|
||||
|
||||
func (t *ToolTapByCV) Name() option.ActionName {
|
||||
return option.ACTION_TapByCV
|
||||
@@ -288,10 +290,13 @@ func (t *ToolTapByCV) Implement() server.ToolHandlerFunc {
|
||||
// We'll add a basic implementation that triggers CV recognition
|
||||
err = driverExt.TapByCV(opts...)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Tap by CV failed: %s", err.Error())), nil
|
||||
return NewMCPErrorResponse(fmt.Sprintf("Tap by CV failed: %s", err.Error())), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText("Successfully tapped by computer vision"), nil
|
||||
message := "Successfully tapped by computer vision"
|
||||
returnData := ToolTapByCV{}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,12 +312,12 @@ func (t *ToolTapByCV) ConvertActionToCallToolRequest(action option.MobileAction)
|
||||
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
||||
}
|
||||
|
||||
func (t *ToolTapByCV) ReturnSchema() map[string]string {
|
||||
return defaultReturnSchema()
|
||||
}
|
||||
|
||||
// ToolDoubleTapXY implements the double_tap_xy tool call.
|
||||
type ToolDoubleTapXY struct{}
|
||||
type ToolDoubleTapXY struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
X float64 `json:"x" desc:"X coordinate where double tap was performed"`
|
||||
Y float64 `json:"y" desc:"Y coordinate where double tap was performed"`
|
||||
}
|
||||
|
||||
func (t *ToolDoubleTapXY) Name() option.ActionName {
|
||||
return option.ACTION_DoubleTapXY
|
||||
@@ -347,10 +352,16 @@ func (t *ToolDoubleTapXY) Implement() server.ToolHandlerFunc {
|
||||
// Double tap XY action logic
|
||||
err = driverExt.DoubleTap(unifiedReq.X, unifiedReq.Y)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Double tap failed: %s", err.Error())), nil
|
||||
return NewMCPErrorResponse(fmt.Sprintf("Double tap failed: %s", err.Error())), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Successfully double tapped at (%.2f, %.2f)", unifiedReq.X, unifiedReq.Y)), nil
|
||||
message := fmt.Sprintf("Successfully double tapped at (%.2f, %.2f)", unifiedReq.X, unifiedReq.Y)
|
||||
returnData := ToolDoubleTapXY{
|
||||
X: unifiedReq.X,
|
||||
Y: unifiedReq.Y,
|
||||
}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,7 +376,3 @@ func (t *ToolDoubleTapXY) ConvertActionToCallToolRequest(action option.MobileAct
|
||||
}
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid double tap params: %v", action.Params)
|
||||
}
|
||||
|
||||
func (t *ToolDoubleTapXY) ReturnSchema() map[string]string {
|
||||
return defaultReturnSchema()
|
||||
}
|
||||
|
||||
@@ -14,7 +14,11 @@ import (
|
||||
)
|
||||
|
||||
// ToolSleep implements the sleep tool call.
|
||||
type ToolSleep struct{}
|
||||
type ToolSleep struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
Seconds float64 `json:"seconds" desc:"Duration in seconds that was slept"`
|
||||
Duration string `json:"duration" desc:"Human-readable duration string"`
|
||||
}
|
||||
|
||||
func (t *ToolSleep) Name() option.ActionName {
|
||||
return option.ACTION_Sleep
|
||||
@@ -42,18 +46,23 @@ func (t *ToolSleep) Implement() server.ToolHandlerFunc {
|
||||
log.Info().Interface("seconds", seconds).Msg("sleeping")
|
||||
|
||||
var duration time.Duration
|
||||
var actualSeconds float64
|
||||
switch v := seconds.(type) {
|
||||
case float64:
|
||||
actualSeconds = v
|
||||
duration = time.Duration(v*1000) * time.Millisecond
|
||||
case int:
|
||||
actualSeconds = float64(v)
|
||||
duration = time.Duration(v) * time.Second
|
||||
case int64:
|
||||
actualSeconds = float64(v)
|
||||
duration = time.Duration(v) * time.Second
|
||||
case string:
|
||||
s, err := builtin.ConvertToFloat64(v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid sleep duration: %v", v)
|
||||
}
|
||||
actualSeconds = s
|
||||
duration = time.Duration(s*1000) * time.Millisecond
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported sleep duration type: %T", v)
|
||||
@@ -61,7 +70,13 @@ func (t *ToolSleep) Implement() server.ToolHandlerFunc {
|
||||
|
||||
time.Sleep(duration)
|
||||
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Successfully slept for %v seconds", seconds)), nil
|
||||
message := fmt.Sprintf("Successfully slept for %v seconds", actualSeconds)
|
||||
returnData := ToolSleep{
|
||||
Seconds: actualSeconds,
|
||||
Duration: duration.String(),
|
||||
}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,15 +87,11 @@ func (t *ToolSleep) ConvertActionToCallToolRequest(action option.MobileAction) (
|
||||
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
||||
}
|
||||
|
||||
func (t *ToolSleep) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"message": "string: Success message confirming sleep operation completed",
|
||||
"seconds": "float64: Duration in seconds that was slept",
|
||||
}
|
||||
}
|
||||
|
||||
// ToolSleepMS implements the sleep_ms tool call.
|
||||
type ToolSleepMS struct{}
|
||||
type ToolSleepMS struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
Milliseconds int64 `json:"milliseconds" desc:"Duration in milliseconds that was slept"`
|
||||
}
|
||||
|
||||
func (t *ToolSleepMS) Name() option.ActionName {
|
||||
return option.ACTION_SleepMS
|
||||
@@ -111,7 +122,10 @@ func (t *ToolSleepMS) Implement() server.ToolHandlerFunc {
|
||||
log.Info().Int64("milliseconds", unifiedReq.Milliseconds).Msg("sleeping in milliseconds")
|
||||
time.Sleep(time.Duration(unifiedReq.Milliseconds) * time.Millisecond)
|
||||
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Successfully slept for %d milliseconds", unifiedReq.Milliseconds)), nil
|
||||
message := fmt.Sprintf("Successfully slept for %d milliseconds", unifiedReq.Milliseconds)
|
||||
returnData := ToolSleepMS{Milliseconds: unifiedReq.Milliseconds}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,15 +144,11 @@ func (t *ToolSleepMS) ConvertActionToCallToolRequest(action option.MobileAction)
|
||||
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
||||
}
|
||||
|
||||
func (t *ToolSleepMS) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"message": "string: Success message confirming sleep operation completed",
|
||||
"milliseconds": "int64: Duration in milliseconds that was slept",
|
||||
}
|
||||
}
|
||||
|
||||
// ToolSleepRandom implements the sleep_random tool call.
|
||||
type ToolSleepRandom struct{}
|
||||
type ToolSleepRandom struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
Params []float64 `json:"params" desc:"Random sleep parameters used"`
|
||||
}
|
||||
|
||||
func (t *ToolSleepRandom) Name() option.ActionName {
|
||||
return option.ACTION_SleepRandom
|
||||
@@ -163,7 +173,10 @@ func (t *ToolSleepRandom) Implement() server.ToolHandlerFunc {
|
||||
// Sleep random action logic
|
||||
sleepStrict(time.Now(), getSimulationDuration(unifiedReq.Params))
|
||||
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Successfully slept for random duration with params: %v", unifiedReq.Params)), nil
|
||||
message := fmt.Sprintf("Successfully slept for random duration with params: %v", unifiedReq.Params)
|
||||
returnData := ToolSleepRandom{Params: unifiedReq.Params}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,16 +190,9 @@ func (t *ToolSleepRandom) ConvertActionToCallToolRequest(action option.MobileAct
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid sleep random params: %v", action.Params)
|
||||
}
|
||||
|
||||
func (t *ToolSleepRandom) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"message": "string: Success message confirming random sleep operation completed",
|
||||
"params": "[]float64: Parameters used for random duration calculation",
|
||||
"actualDuration": "float64: Actual duration that was slept (in seconds)",
|
||||
}
|
||||
}
|
||||
|
||||
// ToolClosePopups implements the close_popups tool call.
|
||||
type ToolClosePopups struct{}
|
||||
type ToolClosePopups struct { // Return data fields - these define the structure of data returned by this tool
|
||||
}
|
||||
|
||||
func (t *ToolClosePopups) Name() option.ActionName {
|
||||
return option.ACTION_ClosePopups
|
||||
@@ -211,20 +217,16 @@ func (t *ToolClosePopups) Implement() server.ToolHandlerFunc {
|
||||
// Close popups action logic
|
||||
err = driverExt.ClosePopupsHandler()
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Close popups failed: %s", err.Error())), nil
|
||||
return NewMCPErrorResponse(fmt.Sprintf("Close popups failed: %s", err.Error())), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText("Successfully closed popups"), nil
|
||||
message := "Successfully closed popups"
|
||||
returnData := ToolClosePopups{}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ToolClosePopups) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) {
|
||||
return buildMCPCallToolRequest(t.Name(), map[string]any{}), nil
|
||||
}
|
||||
|
||||
func (t *ToolClosePopups) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"message": "string: Success message confirming popups were closed",
|
||||
"popupsClosed": "int: Number of popup windows or dialogs that were closed",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,10 @@ import (
|
||||
)
|
||||
|
||||
// ToolWebLoginNoneUI implements the web_login_none_ui tool call.
|
||||
type ToolWebLoginNoneUI struct{}
|
||||
type ToolWebLoginNoneUI struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
PackageName string `json:"packageName" desc:"Package name used for web login"`
|
||||
}
|
||||
|
||||
func (t *ToolWebLoginNoneUI) Name() option.ActionName {
|
||||
return option.ACTION_WebLoginNoneUI
|
||||
@@ -49,10 +52,13 @@ func (t *ToolWebLoginNoneUI) Implement() server.ToolHandlerFunc {
|
||||
|
||||
_, err = driver.LoginNoneUI(unifiedReq.PackageName, unifiedReq.PhoneNumber, unifiedReq.Captcha, unifiedReq.Password)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Web login failed: %s", err.Error())), nil
|
||||
return NewMCPErrorResponse(fmt.Sprintf("Web login failed: %s", err.Error())), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText("Successfully performed web login without UI"), nil
|
||||
message := "Successfully performed web login without UI"
|
||||
returnData := ToolWebLoginNoneUI{PackageName: unifiedReq.PackageName}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,15 +66,12 @@ func (t *ToolWebLoginNoneUI) ConvertActionToCallToolRequest(action option.Mobile
|
||||
return buildMCPCallToolRequest(t.Name(), map[string]any{}), nil
|
||||
}
|
||||
|
||||
func (t *ToolWebLoginNoneUI) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"message": "string: Success message confirming web login was completed",
|
||||
"loginResult": "object: Result of the login operation (success/failure details)",
|
||||
}
|
||||
}
|
||||
|
||||
// ToolSecondaryClick implements the secondary_click tool call.
|
||||
type ToolSecondaryClick struct{}
|
||||
type ToolSecondaryClick struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
X float64 `json:"x" desc:"X coordinate of the secondary click"`
|
||||
Y float64 `json:"y" desc:"Y coordinate of the secondary click"`
|
||||
}
|
||||
|
||||
func (t *ToolSecondaryClick) Name() option.ActionName {
|
||||
return option.ACTION_SecondaryClick
|
||||
@@ -103,10 +106,16 @@ func (t *ToolSecondaryClick) Implement() server.ToolHandlerFunc {
|
||||
// Secondary click action logic
|
||||
err = driverExt.SecondaryClick(unifiedReq.X, unifiedReq.Y)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Secondary click failed: %s", err.Error())), nil
|
||||
return NewMCPErrorResponse(fmt.Sprintf("Secondary click failed: %s", err.Error())), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Successfully performed secondary click at (%.2f, %.2f)", unifiedReq.X, unifiedReq.Y)), nil
|
||||
message := fmt.Sprintf("Successfully performed secondary click at (%.2f, %.2f)", unifiedReq.X, unifiedReq.Y)
|
||||
returnData := ToolSecondaryClick{
|
||||
X: unifiedReq.X,
|
||||
Y: unifiedReq.Y,
|
||||
}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,16 +130,11 @@ func (t *ToolSecondaryClick) ConvertActionToCallToolRequest(action option.Mobile
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid secondary click params: %v", action.Params)
|
||||
}
|
||||
|
||||
func (t *ToolSecondaryClick) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"message": "string: Success message confirming secondary click (right-click) operation",
|
||||
"x": "float64: X coordinate where secondary click was performed",
|
||||
"y": "float64: Y coordinate where secondary click was performed",
|
||||
}
|
||||
}
|
||||
|
||||
// ToolHoverBySelector implements the hover_by_selector tool call.
|
||||
type ToolHoverBySelector struct{}
|
||||
type ToolHoverBySelector struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
Selector string `json:"selector" desc:"CSS selector or XPath used for hover"`
|
||||
}
|
||||
|
||||
func (t *ToolHoverBySelector) Name() option.ActionName {
|
||||
return option.ACTION_HoverBySelector
|
||||
@@ -160,10 +164,13 @@ func (t *ToolHoverBySelector) Implement() server.ToolHandlerFunc {
|
||||
// Hover by selector action logic
|
||||
err = driverExt.HoverBySelector(unifiedReq.Selector)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Hover by selector failed: %s", err.Error())), nil
|
||||
return NewMCPErrorResponse(fmt.Sprintf("Hover by selector failed: %s", err.Error())), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Successfully hovered over element with selector: %s", unifiedReq.Selector)), nil
|
||||
message := fmt.Sprintf("Successfully hovered over element with selector: %s", unifiedReq.Selector)
|
||||
returnData := ToolHoverBySelector{Selector: unifiedReq.Selector}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,15 +184,11 @@ func (t *ToolHoverBySelector) ConvertActionToCallToolRequest(action option.Mobil
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid hover by selector params: %v", action.Params)
|
||||
}
|
||||
|
||||
func (t *ToolHoverBySelector) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"message": "string: Success message confirming hover operation",
|
||||
"selector": "string: CSS selector or XPath of the element that was hovered over",
|
||||
}
|
||||
}
|
||||
|
||||
// ToolTapBySelector implements the tap_by_selector tool call.
|
||||
type ToolTapBySelector struct{}
|
||||
type ToolTapBySelector struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
Selector string `json:"selector" desc:"CSS selector or XPath used for tap"`
|
||||
}
|
||||
|
||||
func (t *ToolTapBySelector) Name() option.ActionName {
|
||||
return option.ACTION_TapBySelector
|
||||
@@ -215,10 +218,13 @@ func (t *ToolTapBySelector) Implement() server.ToolHandlerFunc {
|
||||
// Tap by selector action logic
|
||||
err = driverExt.TapBySelector(unifiedReq.Selector)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Tap by selector failed: %s", err.Error())), nil
|
||||
return NewMCPErrorResponse(fmt.Sprintf("Tap by selector failed: %s", err.Error())), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Successfully tapped element with selector: %s", unifiedReq.Selector)), nil
|
||||
message := fmt.Sprintf("Successfully tapped element with selector: %s", unifiedReq.Selector)
|
||||
returnData := ToolTapBySelector{Selector: unifiedReq.Selector}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,15 +238,11 @@ func (t *ToolTapBySelector) ConvertActionToCallToolRequest(action option.MobileA
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid tap by selector params: %v", action.Params)
|
||||
}
|
||||
|
||||
func (t *ToolTapBySelector) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"message": "string: Success message confirming tap operation",
|
||||
"selector": "string: CSS selector or XPath of the element that was tapped",
|
||||
}
|
||||
}
|
||||
|
||||
// ToolSecondaryClickBySelector implements the secondary_click_by_selector tool call.
|
||||
type ToolSecondaryClickBySelector struct{}
|
||||
type ToolSecondaryClickBySelector struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
Selector string `json:"selector" desc:"CSS selector or XPath used for secondary click"`
|
||||
}
|
||||
|
||||
func (t *ToolSecondaryClickBySelector) Name() option.ActionName {
|
||||
return option.ACTION_SecondaryClickBySelector
|
||||
@@ -270,10 +272,13 @@ func (t *ToolSecondaryClickBySelector) Implement() server.ToolHandlerFunc {
|
||||
// Secondary click by selector action logic
|
||||
err = driverExt.SecondaryClickBySelector(unifiedReq.Selector)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Secondary click by selector failed: %s", err.Error())), nil
|
||||
return NewMCPErrorResponse(fmt.Sprintf("Secondary click by selector failed: %s", err.Error())), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Successfully performed secondary click on element with selector: %s", unifiedReq.Selector)), nil
|
||||
message := fmt.Sprintf("Successfully performed secondary click on element with selector: %s", unifiedReq.Selector)
|
||||
returnData := ToolSecondaryClickBySelector{Selector: unifiedReq.Selector}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,15 +292,11 @@ func (t *ToolSecondaryClickBySelector) ConvertActionToCallToolRequest(action opt
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid secondary click by selector params: %v", action.Params)
|
||||
}
|
||||
|
||||
func (t *ToolSecondaryClickBySelector) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"message": "string: Success message confirming secondary click operation",
|
||||
"selector": "string: CSS selector or XPath of the element that was right-clicked",
|
||||
}
|
||||
}
|
||||
|
||||
// ToolWebCloseTab implements the web_close_tab tool call.
|
||||
type ToolWebCloseTab struct{}
|
||||
type ToolWebCloseTab struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
TabIndex int `json:"tabIndex" desc:"Index of the closed tab"`
|
||||
}
|
||||
|
||||
func (t *ToolWebCloseTab) Name() option.ActionName {
|
||||
return option.ACTION_WebCloseTab
|
||||
@@ -335,10 +336,13 @@ func (t *ToolWebCloseTab) Implement() server.ToolHandlerFunc {
|
||||
|
||||
err = browserDriver.CloseTab(unifiedReq.TabIndex)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Close tab failed: %s", err.Error())), nil
|
||||
return NewMCPErrorResponse(fmt.Sprintf("Close tab failed: %s", err.Error())), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Successfully closed tab at index: %d", unifiedReq.TabIndex)), nil
|
||||
message := fmt.Sprintf("Successfully closed tab at index: %d", unifiedReq.TabIndex)
|
||||
returnData := ToolWebCloseTab{TabIndex: unifiedReq.TabIndex}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,10 +363,3 @@ func (t *ToolWebCloseTab) ConvertActionToCallToolRequest(action option.MobileAct
|
||||
}
|
||||
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
||||
}
|
||||
|
||||
func (t *ToolWebCloseTab) ReturnSchema() map[string]string {
|
||||
return map[string]string{
|
||||
"message": "string: Success message confirming browser tab was closed",
|
||||
"tabIndex": "int: Index of the tab that was closed",
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user