refactor: merge DoAction to mcp server tools

This commit is contained in:
lilong.129
2025-05-25 23:53:07 +08:00
parent 7986c4899f
commit 2e17d9df16
36 changed files with 725 additions and 1006 deletions

View File

@@ -408,11 +408,11 @@ func (ad *ADBDriver) Swipe(fromX, fromY, toX, toY float64, opts ...option.Action
Float64("toX", toX).Float64("toY", toY).Msg("ADBDriver.Swipe")
actionOptions := option.NewActionOptions(opts...)
fromX, fromY, toX, toY, err := preHandler_Swipe(ad, actionOptions, fromX, fromY, toX, toY)
fromX, fromY, toX, toY, err := preHandler_Swipe(ad, option.ACTION_SwipeCoordinate, actionOptions, fromX, fromY, toX, toY)
if err != nil {
return err
}
defer postHandler(ad, option.ACTION_Swipe, actionOptions)
defer postHandler(ad, option.ACTION_SwipeCoordinate, actionOptions)
// adb shell input swipe fromX fromY toX toY
_, err = ad.runShellCommand(

View File

@@ -394,11 +394,11 @@ func (ud *UIA2Driver) Swipe(fromX, fromY, toX, toY float64, opts ...option.Actio
Float64("toX", toX).Float64("toY", toY).Msg("UIA2Driver.Swipe")
actionOptions := option.NewActionOptions(opts...)
fromX, fromY, toX, toY, err := preHandler_Swipe(ud, actionOptions, fromX, fromY, toX, toY)
fromX, fromY, toX, toY, err := preHandler_Swipe(ud, option.ACTION_SwipeCoordinate, actionOptions, fromX, fromY, toX, toY)
if err != nil {
return err
}
defer postHandler(ud, option.ACTION_Swipe, actionOptions)
defer postHandler(ud, option.ACTION_SwipeCoordinate, actionOptions)
duration := 200.0
if actionOptions.PressDuration > 0 {

View File

@@ -62,7 +62,7 @@ func (dExt *XTDriver) AIAction(text string, opts ...option.ActionOption) error {
},
}
_, err = dExt.client.CallTool(context.Background(), req)
_, err = dExt.Client.CallTool(context.Background(), req)
if err != nil {
return err
}

View File

@@ -348,7 +348,7 @@ func MarkUIOperation(driver IDriver, actionType option.ActionMethod, actionCoord
x, y := actionCoordinates[0], actionCoordinates[1]
point := image.Point{X: int(x), Y: int(y)}
err = SaveImageWithCircleMarker(compressedBufSource, point, imagePath)
} else if actionType == option.ACTION_Swipe || actionType == option.ACTION_Drag {
} else if actionType == option.ACTION_SwipeDirection || actionType == option.ACTION_SwipeCoordinate || actionType == option.ACTION_Drag {
if len(actionCoordinates) != 4 {
return fmt.Errorf("invalid swipe action coordinates: %v", actionCoordinates)
}

View File

@@ -98,7 +98,8 @@ func preHandler_Drag(driver IDriver, options *option.ActionOptions, rawFomX, raw
return fromX, fromY, toX, toY, nil
}
func preHandler_Swipe(driver IDriver, options *option.ActionOptions, rawFomX, rawFromY, rawToX, rawToY float64) (
func preHandler_Swipe(driver IDriver, actionType option.ActionMethod,
options *option.ActionOptions, rawFomX, rawFromY, rawToX, rawToY float64) (
fromX, fromY, toX, toY float64, err error) {
fromX, fromY, toX, toY, err = convertToAbsoluteCoordinates(driver, rawFomX, rawFromY, rawToX, rawToY)
@@ -109,7 +110,7 @@ func preHandler_Swipe(driver IDriver, options *option.ActionOptions, rawFomX, ra
// save screenshot before action and mark UI operation
if options.PreMarkOperation {
if markErr := MarkUIOperation(driver, option.ACTION_Swipe, []float64{fromX, fromY, toX, toY}); markErr != nil {
if markErr := MarkUIOperation(driver, actionType, []float64{fromX, fromY, toX, toY}); markErr != nil {
log.Warn().Err(markErr).Msg("Failed to mark swipe operation")
}
}

View File

@@ -187,11 +187,11 @@ func (hd *HDCDriver) Swipe(fromX, fromY, toX, toY float64, opts ...option.Action
Float64("toX", toX).Float64("toY", toY).Msg("HDCDriver.Swipe")
actionOptions := option.NewActionOptions(opts...)
fromX, fromY, toX, toY, err := preHandler_Swipe(hd, actionOptions, fromX, fromY, toX, toY)
fromX, fromY, toX, toY, err := preHandler_Swipe(hd, option.ACTION_SwipeCoordinate, actionOptions, fromX, fromY, toX, toY)
if err != nil {
return err
}
defer postHandler(hd, option.ACTION_Swipe, actionOptions)
defer postHandler(hd, option.ACTION_SwipeCoordinate, actionOptions)
duration := 200
if actionOptions.PressDuration > 0 {

File diff suppressed because it is too large Load Diff

View File

@@ -19,8 +19,8 @@ func TestNewMCPServer(t *testing.T) {
"list_available_devices",
"select_device",
"list_packages",
"launch_app",
"terminate_app",
"app_launch",
"app_terminate",
"get_screen_size",
"press_button",
"tap_xy",
@@ -54,7 +54,7 @@ func TestToolInterfaces(t *testing.T) {
&ToolGetScreenSize{},
&ToolPressButton{},
&ToolTapXY{},
&ToolSwipe{},
&ToolSwipeDirection{},
&ToolDrag{},
&ToolScreenShot{},
&ToolHome{},
@@ -64,7 +64,7 @@ func TestToolInterfaces(t *testing.T) {
}
for _, tool := range tools {
assert.NotEmpty(t, tool.Name(), "Tool name should not be empty")
assert.NotEmpty(t, string(tool.Name()), "Tool name should not be empty")
assert.NotEmpty(t, tool.Description(), "Tool description should not be empty")
assert.NotNil(t, tool.Options(), "Tool options should not be nil")
assert.NotNil(t, tool.Implement(), "Tool implementation should not be nil")

View File

@@ -12,15 +12,17 @@ type ActionMethod string
const (
ACTION_LOG ActionMethod = "log"
ACTION_AppInstall ActionMethod = "install"
ACTION_AppUninstall ActionMethod = "uninstall"
ACTION_WebLoginNoneUI ActionMethod = "login_none_ui"
ACTION_ListPackages ActionMethod = "list_packages"
ACTION_AppInstall ActionMethod = "app_install"
ACTION_AppUninstall ActionMethod = "app_uninstall"
ACTION_WebLoginNoneUI ActionMethod = "web_login_none_ui"
ACTION_AppClear ActionMethod = "app_clear"
ACTION_AppStart ActionMethod = "app_start"
ACTION_AppLaunch ActionMethod = "app_launch" // 启动 app 并堵塞等待 app 首屏加载完成
ACTION_AppTerminate ActionMethod = "app_terminate"
ACTION_AppStop ActionMethod = "app_stop"
ACTION_ScreenShot ActionMethod = "screenshot"
ACTION_GetScreenSize ActionMethod = "get_screen_size"
ACTION_Sleep ActionMethod = "sleep"
ACTION_SleepMS ActionMethod = "sleep_ms"
ACTION_SleepRandom ActionMethod = "sleep_random"
@@ -33,12 +35,14 @@ const (
ACTION_Home ActionMethod = "home"
ACTION_TapXY ActionMethod = "tap_xy"
ACTION_TapAbsXY ActionMethod = "tap_abs_xy"
ACTION_TapByOCR ActionMethod = "tap_ocr"
ACTION_TapByCV ActionMethod = "tap_cv"
ACTION_TapByOCR ActionMethod = "tap_by_ocr"
ACTION_TapByCV ActionMethod = "tap_by_cv"
ACTION_DoubleTapXY ActionMethod = "double_tap_xy"
ACTION_Swipe ActionMethod = "swipe"
ACTION_SwipeDirection ActionMethod = "swipe_direction" // swipe by direction (up, down, left, right)
ACTION_SwipeCoordinate ActionMethod = "swipe_coordinate" // swipe by coordinates (fromX, fromY, toX, toY)
ACTION_Drag ActionMethod = "drag"
ACTION_Input ActionMethod = "input"
ACTION_PressButton ActionMethod = "press_button"
ACTION_Back ActionMethod = "back"
ACTION_KeyCode ActionMethod = "keycode"
ACTION_AIAction ActionMethod = "ai_action" // action with ai
@@ -49,6 +53,10 @@ const (
ACTION_SecondaryClickBySelector ActionMethod = "secondary_click_by_selector"
ACTION_GetElementTextBySelector ActionMethod = "get_element_text_by_selector"
// device actions
ACTION_ListAvailableDevices ActionMethod = "list_available_devices"
ACTION_SelectDevice ActionMethod = "select_device"
// custom actions
ACTION_SwipeToTapApp ActionMethod = "swipe_to_tap_app" // swipe left & right to find app and tap
ACTION_SwipeToTapText ActionMethod = "swipe_to_tap_text" // swipe up & down to find text and tap

View File

@@ -2,10 +2,8 @@ package uixt
import (
"context"
"encoding/json"
"fmt"
"github.com/httprunner/httprunner/v5/internal/builtin"
"github.com/httprunner/httprunner/v5/uixt/ai"
"github.com/httprunner/httprunner/v5/uixt/option"
"github.com/mark3labs/mcp-go/client"
@@ -16,7 +14,7 @@ import (
func NewXTDriver(driver IDriver, opts ...option.AIServiceOption) (*XTDriver, error) {
driverExt := &XTDriver{
IDriver: driver,
client: &MCPClient4XTDriver{
Client: &MCPClient4XTDriver{
Server: NewMCPServer(),
},
}
@@ -48,7 +46,7 @@ type XTDriver struct {
CVService ai.ICVService // OCR/CV
LLMService ai.ILLMService // LLM
client *MCPClient4XTDriver // MCP Client
Client *MCPClient4XTDriver // MCP Client
}
// MCPClient4XTDriver is a mock MCP client that only implements the methods used by the host
@@ -63,10 +61,11 @@ func (c *MCPClient4XTDriver) ListTools(ctx context.Context, req mcp.ListToolsReq
}
func (c *MCPClient4XTDriver) CallTool(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
handler := c.Server.GetHandler(req.Params.Name)
if handler == nil {
return mcp.NewToolResultError(fmt.Sprintf("handler for tool %s not found", req.Params.Name)), nil
actionTool := c.Server.GetToolByActionMethod(option.ActionMethod(req.Params.Name))
if actionTool == nil {
return mcp.NewToolResultError(fmt.Sprintf("action %s for tool not found", req.Params.Name)), nil
}
handler := actionTool.Implement()
return handler(ctx, req)
}
@@ -81,14 +80,20 @@ func (c *MCPClient4XTDriver) Close() error {
}
func (dExt *XTDriver) ExecuteAction(action MobileAction) (err error) {
// Convert action to MCP tool call
req, err := convertActionToCallToolRequest(action)
// Find the corresponding tool for this action method
tool := dExt.Client.Server.GetToolByActionMethod(action.Method)
if tool == nil {
return fmt.Errorf("no tool found for action method: %s", action.Method)
}
// Use the tool's own conversion method
req, err := tool.ConvertActionToCallToolRequest(action)
if err != nil {
return fmt.Errorf("failed to convert action to MCP tool call: %w", err)
}
// Execute via MCP tool
result, err := dExt.client.CallTool(context.Background(), req)
result, err := dExt.Client.CallTool(context.Background(), req)
if err != nil {
return fmt.Errorf("MCP tool call failed: %w", err)
}
@@ -101,734 +106,8 @@ func (dExt *XTDriver) ExecuteAction(action MobileAction) (err error) {
return fmt.Errorf("tool execution failed")
}
log.Debug().Str("method", string(action.Method)).Msg("executed action via MCP tool")
log.Debug().Str("method", string(action.Method)).
Str("tool", string(tool.Name())).
Msg("executed action via MCP tool")
return nil
}
func convertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
var arguments map[string]interface{}
switch action.Method {
case option.ACTION_WebLoginNoneUI:
if params, ok := action.Params.([]interface{}); ok && len(params) == 4 {
arguments = map[string]interface{}{
"packageName": params[0].(string),
"phoneNumber": params[1].(string),
"captcha": params[2].(string),
"password": params[3].(string),
}
} else if params, ok := action.Params.([]string); ok && len(params) == 4 {
arguments = map[string]interface{}{
"packageName": params[0],
"phoneNumber": params[1],
"captcha": params[2],
"password": params[3],
}
} else {
return mcp.CallToolRequest{}, fmt.Errorf("invalid web login params: %v", action.Params)
}
return mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
Meta *struct {
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}{
Name: "web_login_none_ui",
Arguments: arguments,
},
}, nil
case option.ACTION_AppInstall:
if app, ok := action.Params.(string); ok {
arguments = map[string]interface{}{
"appUrl": app,
}
} else {
return mcp.CallToolRequest{}, fmt.Errorf("invalid app install params: %v", action.Params)
}
return mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
Meta *struct {
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}{
Name: "app_install",
Arguments: arguments,
},
}, nil
case option.ACTION_AppUninstall:
if packageName, ok := action.Params.(string); ok {
arguments = map[string]interface{}{
"packageName": packageName,
}
} else {
return mcp.CallToolRequest{}, fmt.Errorf("invalid app uninstall params: %v", action.Params)
}
return mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
Meta *struct {
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}{
Name: "app_uninstall",
Arguments: arguments,
},
}, nil
case option.ACTION_AppClear:
if packageName, ok := action.Params.(string); ok {
arguments = map[string]interface{}{
"packageName": packageName,
}
} else {
return mcp.CallToolRequest{}, fmt.Errorf("invalid app clear params: %v", action.Params)
}
return mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
Meta *struct {
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}{
Name: "app_clear",
Arguments: arguments,
},
}, nil
case option.ACTION_AppLaunch:
if packageName, ok := action.Params.(string); ok {
arguments = map[string]interface{}{
"packageName": packageName,
}
} else {
return mcp.CallToolRequest{}, fmt.Errorf("invalid app launch params: %v", action.Params)
}
return mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
Meta *struct {
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}{
Name: "launch_app",
Arguments: arguments,
},
}, nil
case option.ACTION_SwipeToTapApp:
if appName, ok := action.Params.(string); ok {
arguments = map[string]interface{}{
"appName": appName,
}
} else {
return mcp.CallToolRequest{}, fmt.Errorf("invalid swipe to tap app params: %v", action.Params)
}
return mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
Meta *struct {
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}{
Name: "swipe_to_tap_app",
Arguments: arguments,
},
}, nil
case option.ACTION_SwipeToTapText:
if text, ok := action.Params.(string); ok {
arguments = map[string]interface{}{
"text": text,
}
} else {
return mcp.CallToolRequest{}, fmt.Errorf("invalid swipe to tap text params: %v", action.Params)
}
return mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
Meta *struct {
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}{
Name: "swipe_to_tap_text",
Arguments: arguments,
},
}, nil
case option.ACTION_SwipeToTapTexts:
var texts []string
if textsSlice, ok := action.Params.([]string); ok {
texts = textsSlice
} else if textsInterface, err := builtin.ConvertToStringSlice(action.Params); err == nil {
texts = textsInterface
} else {
return mcp.CallToolRequest{}, fmt.Errorf("invalid swipe to tap texts params: %v", action.Params)
}
arguments = map[string]interface{}{
"texts": texts,
}
return mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
Meta *struct {
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}{
Name: "swipe_to_tap_texts",
Arguments: arguments,
},
}, nil
case option.ACTION_AppTerminate:
if packageName, ok := action.Params.(string); ok {
arguments = map[string]interface{}{
"packageName": packageName,
}
} else {
return mcp.CallToolRequest{}, fmt.Errorf("invalid app terminate params: %v", action.Params)
}
return mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
Meta *struct {
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}{
Name: "terminate_app",
Arguments: arguments,
},
}, nil
case option.ACTION_Home:
return mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
Meta *struct {
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}{
Name: "home",
Arguments: map[string]interface{}{},
},
}, nil
case option.ACTION_SecondaryClick:
if params, err := builtin.ConvertToFloat64Slice(action.Params); err == nil && len(params) == 2 {
arguments = map[string]interface{}{
"x": params[0],
"y": params[1],
}
} else {
return mcp.CallToolRequest{}, fmt.Errorf("invalid secondary click params: %v", action.Params)
}
return mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
Meta *struct {
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}{
Name: "secondary_click",
Arguments: arguments,
},
}, nil
case option.ACTION_HoverBySelector:
if selector, ok := action.Params.(string); ok {
arguments = map[string]interface{}{
"selector": selector,
}
} else {
return mcp.CallToolRequest{}, fmt.Errorf("invalid hover by selector params: %v", action.Params)
}
return mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
Meta *struct {
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}{
Name: "hover_by_selector",
Arguments: arguments,
},
}, nil
case option.ACTION_TapBySelector:
if selector, ok := action.Params.(string); ok {
arguments = map[string]interface{}{
"selector": selector,
}
} else {
return mcp.CallToolRequest{}, fmt.Errorf("invalid tap by selector params: %v", action.Params)
}
return mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
Meta *struct {
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}{
Name: "tap_by_selector",
Arguments: arguments,
},
}, nil
case option.ACTION_SecondaryClickBySelector:
if selector, ok := action.Params.(string); ok {
arguments = map[string]interface{}{
"selector": selector,
}
} else {
return mcp.CallToolRequest{}, fmt.Errorf("invalid secondary click by selector params: %v", action.Params)
}
return mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
Meta *struct {
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}{
Name: "secondary_click_by_selector",
Arguments: arguments,
},
}, nil
case option.ACTION_WebCloseTab:
var tabIndex int
if param, ok := action.Params.(json.Number); ok {
paramInt64, _ := param.Int64()
tabIndex = int(paramInt64)
} else if param, ok := action.Params.(int64); ok {
tabIndex = int(param)
} else if param, ok := action.Params.(int); ok {
tabIndex = param
} else {
return mcp.CallToolRequest{}, fmt.Errorf("invalid web close tab params: %v", action.Params)
}
arguments = map[string]interface{}{
"tabIndex": tabIndex,
}
return mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
Meta *struct {
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}{
Name: "web_close_tab",
Arguments: arguments,
},
}, nil
case option.ACTION_SetIme:
if ime, ok := action.Params.(string); ok {
arguments = map[string]interface{}{
"ime": ime,
}
} else {
return mcp.CallToolRequest{}, fmt.Errorf("invalid set ime params: %v", action.Params)
}
return mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
Meta *struct {
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}{
Name: "set_ime",
Arguments: arguments,
},
}, nil
case option.ACTION_GetSource:
if packageName, ok := action.Params.(string); ok {
arguments = map[string]interface{}{
"packageName": packageName,
}
} else {
return mcp.CallToolRequest{}, fmt.Errorf("invalid get source params: %v", action.Params)
}
return mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
Meta *struct {
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}{
Name: "get_source",
Arguments: arguments,
},
}, nil
case option.ACTION_TapXY:
if params, err := builtin.ConvertToFloat64Slice(action.Params); err == nil && len(params) == 2 {
x, y := params[0], params[1]
arguments = map[string]interface{}{
"x": x,
"y": y,
}
// Add duration if available from action options
if actionOptions := action.GetOptions(); len(actionOptions) > 0 {
for _, opt := range actionOptions {
if opt != nil {
// Add options like duration
if duration := action.ActionOptions.Duration; duration > 0 {
arguments["duration"] = duration
}
}
}
}
} else {
return mcp.CallToolRequest{}, fmt.Errorf("invalid tap params: %v", action.Params)
}
return mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
Meta *struct {
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}{
Name: "tap_xy",
Arguments: arguments,
},
}, nil
case option.ACTION_TapAbsXY:
if params, err := builtin.ConvertToFloat64Slice(action.Params); err == nil && len(params) == 2 {
x, y := params[0], params[1]
arguments = map[string]interface{}{
"x": x,
"y": y,
}
// Add duration if available
if duration := action.ActionOptions.Duration; duration > 0 {
arguments["duration"] = duration
}
} else {
return mcp.CallToolRequest{}, fmt.Errorf("invalid tap abs params: %v", action.Params)
}
return mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
Meta *struct {
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}{
Name: "tap_abs_xy",
Arguments: arguments,
},
}, nil
case option.ACTION_TapByOCR:
if text, ok := action.Params.(string); ok {
arguments = map[string]interface{}{
"text": text,
}
} else {
return mcp.CallToolRequest{}, fmt.Errorf("invalid tap by OCR params: %v", action.Params)
}
return mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
Meta *struct {
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}{
Name: "tap_by_ocr",
Arguments: arguments,
},
}, nil
case option.ACTION_TapByCV:
// For TapByCV, the original action might not have params but relies on options
arguments = map[string]interface{}{
"imagePath": "", // Will be handled by the tool based on UI types
}
return mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
Meta *struct {
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}{
Name: "tap_by_cv",
Arguments: arguments,
},
}, nil
case option.ACTION_DoubleTapXY:
if params, err := builtin.ConvertToFloat64Slice(action.Params); err == nil && len(params) == 2 {
x, y := params[0], params[1]
arguments = map[string]interface{}{
"x": x,
"y": y,
}
} else {
return mcp.CallToolRequest{}, fmt.Errorf("invalid double tap params: %v", action.Params)
}
return mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
Meta *struct {
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}{
Name: "double_tap_xy",
Arguments: arguments,
},
}, nil
case option.ACTION_Swipe:
// Handle different types of swipe params
switch params := action.Params.(type) {
case string:
// Direction swipe like "up", "down", "left", "right"
arguments = map[string]interface{}{
"direction": params,
}
// Add duration and press duration from options
if duration := action.ActionOptions.Duration; duration > 0 {
arguments["duration"] = duration
}
if pressDuration := action.ActionOptions.PressDuration; pressDuration > 0 {
arguments["pressDuration"] = pressDuration
}
return mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
Meta *struct {
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}{
Name: "swipe",
Arguments: arguments,
},
}, nil
default:
// Advanced swipe with coordinates
if paramSlice, err := builtin.ConvertToFloat64Slice(params); err == nil && len(paramSlice) == 4 {
arguments = map[string]interface{}{
"fromX": paramSlice[0],
"fromY": paramSlice[1],
"toX": paramSlice[2],
"toY": paramSlice[3],
}
// Add duration and press duration from options
if duration := action.ActionOptions.Duration; duration > 0 {
arguments["duration"] = duration
}
if pressDuration := action.ActionOptions.PressDuration; pressDuration > 0 {
arguments["pressDuration"] = pressDuration
}
return mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
Meta *struct {
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}{
Name: "swipe_advanced",
Arguments: arguments,
},
}, nil
}
}
return mcp.CallToolRequest{}, fmt.Errorf("invalid swipe params: %v", action.Params)
case option.ACTION_Input:
text := fmt.Sprintf("%v", action.Params)
arguments = map[string]interface{}{
"text": text,
}
return mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
Meta *struct {
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}{
Name: "input",
Arguments: arguments,
},
}, nil
case option.ACTION_Back:
return mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
Meta *struct {
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}{
Name: "back",
Arguments: map[string]interface{}{},
},
}, nil
case option.ACTION_Sleep:
arguments = map[string]interface{}{
"seconds": action.Params,
}
return mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
Meta *struct {
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}{
Name: "sleep",
Arguments: arguments,
},
}, nil
case option.ACTION_SleepMS:
var milliseconds int64
if param, ok := action.Params.(json.Number); ok {
milliseconds, _ = param.Int64()
} else if param, ok := action.Params.(int64); ok {
milliseconds = param
} else {
return mcp.CallToolRequest{}, fmt.Errorf("invalid sleep ms params: %v", action.Params)
}
arguments = map[string]interface{}{
"milliseconds": milliseconds,
}
return mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
Meta *struct {
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}{
Name: "sleep_ms",
Arguments: arguments,
},
}, nil
case option.ACTION_SleepRandom:
if params, err := builtin.ConvertToFloat64Slice(action.Params); err == nil {
arguments = map[string]interface{}{
"params": params,
}
} else {
return mcp.CallToolRequest{}, fmt.Errorf("invalid sleep random params: %v", action.Params)
}
return mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
Meta *struct {
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}{
Name: "sleep_random",
Arguments: arguments,
},
}, nil
case option.ACTION_ScreenShot:
return mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
Meta *struct {
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}{
Name: "screenshot",
Arguments: map[string]interface{}{},
},
}, nil
case option.ACTION_ClosePopups:
return mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
Meta *struct {
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}{
Name: "close_popups",
Arguments: map[string]interface{}{},
},
}, nil
case option.ACTION_CallFunction:
if description, ok := action.Params.(string); ok {
arguments = map[string]interface{}{
"description": description,
}
} else {
return mcp.CallToolRequest{}, fmt.Errorf("invalid call function params: %v", action.Params)
}
return mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
Meta *struct {
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}{
Name: "call_function",
Arguments: arguments,
},
}, nil
case option.ACTION_AIAction:
if prompt, ok := action.Params.(string); ok {
arguments = map[string]interface{}{
"prompt": prompt,
}
} else {
return mcp.CallToolRequest{}, fmt.Errorf("invalid AI action params: %v", action.Params)
}
return mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
Meta *struct {
ProgressToken mcp.ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}{
Name: "ai_action",
Arguments: arguments,
},
}, nil
default:
return mcp.CallToolRequest{}, fmt.Errorf("unsupported action method: %s", action.Method)
}
}