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

@@ -54,6 +54,7 @@ Copyright © 2017-present debugtalk. Apache-2.0 License.
* [hrp build](hrp_build.md) - Build plugin for testing
* [hrp convert](hrp_convert.md) - Convert multiple source format to HttpRunner JSON/YAML/gotest/pytest cases
* [hrp ios](hrp_ios.md) - simple utils for ios device management
* [hrp mcp-server](hrp_mcp-server.md) - Start MCP server for UI automation
* [hrp mcphost](hrp_mcphost.md) - Start a chat session to interact with MCP tools
* [hrp pytest](hrp_pytest.md) - Run API test with pytest
* [hrp run](hrp_run.md) - Run API test with go engine
@@ -61,4 +62,4 @@ Copyright © 2017-present debugtalk. Apache-2.0 License.
* [hrp startproject](hrp_startproject.md) - Create a scaffold project
* [hrp wiki](hrp_wiki.md) - visit https://httprunner.com
###### Auto generated by spf13/cobra on 17-May-2025
###### Auto generated by spf13/cobra on 25-May-2025

View File

@@ -23,4 +23,4 @@ simple utils for android device management
* [hrp adb install](hrp_adb_install.md) - push package to the device and install them automatically
* [hrp adb screencap](hrp_adb_screencap.md) - Start android screen capture
###### Auto generated by spf13/cobra on 17-May-2025
###### Auto generated by spf13/cobra on 25-May-2025

View File

@@ -24,4 +24,4 @@ hrp adb devices [flags]
* [hrp adb](hrp_adb.md) - simple utils for android device management
###### Auto generated by spf13/cobra on 17-May-2025
###### Auto generated by spf13/cobra on 25-May-2025

View File

@@ -28,4 +28,4 @@ hrp adb install [flags] PACKAGE
* [hrp adb](hrp_adb.md) - simple utils for android device management
###### Auto generated by spf13/cobra on 17-May-2025
###### Auto generated by spf13/cobra on 25-May-2025

View File

@@ -25,4 +25,4 @@ hrp adb screencap [flags]
* [hrp adb](hrp_adb.md) - simple utils for android device management
###### Auto generated by spf13/cobra on 17-May-2025
###### Auto generated by spf13/cobra on 25-May-2025

View File

@@ -36,4 +36,4 @@ hrp build $path ... [flags]
* [hrp](hrp.md) - All-in-One Testing Framework for API, UI and Performance
###### Auto generated by spf13/cobra on 17-May-2025
###### Auto generated by spf13/cobra on 25-May-2025

View File

@@ -34,4 +34,4 @@ hrp convert $path... [flags]
* [hrp](hrp.md) - All-in-One Testing Framework for API, UI and Performance
###### Auto generated by spf13/cobra on 17-May-2025
###### Auto generated by spf13/cobra on 25-May-2025

View File

@@ -29,4 +29,4 @@ simple utils for ios device management
* [hrp ios uninstall](hrp_ios_uninstall.md) - uninstall package automatically
* [hrp ios xctest](hrp_ios_xctest.md) - run xctest
###### Auto generated by spf13/cobra on 17-May-2025
###### Auto generated by spf13/cobra on 25-May-2025

View File

@@ -26,4 +26,4 @@ hrp ios apps [flags]
* [hrp ios](hrp_ios.md) - simple utils for ios device management
###### Auto generated by spf13/cobra on 17-May-2025
###### Auto generated by spf13/cobra on 25-May-2025

View File

@@ -24,4 +24,4 @@ hrp ios devices [flags]
* [hrp ios](hrp_ios.md) - simple utils for ios device management
###### Auto generated by spf13/cobra on 17-May-2025
###### Auto generated by spf13/cobra on 25-May-2025

View File

@@ -25,4 +25,4 @@ hrp ios install [flags] PACKAGE
* [hrp ios](hrp_ios.md) - simple utils for ios device management
###### Auto generated by spf13/cobra on 17-May-2025
###### Auto generated by spf13/cobra on 25-May-2025

View File

@@ -28,4 +28,4 @@ hrp ios mount [flags]
* [hrp ios](hrp_ios.md) - simple utils for ios device management
###### Auto generated by spf13/cobra on 17-May-2025
###### Auto generated by spf13/cobra on 25-May-2025

View File

@@ -26,4 +26,4 @@ hrp ios ps [flags]
* [hrp ios](hrp_ios.md) - simple utils for ios device management
###### Auto generated by spf13/cobra on 17-May-2025
###### Auto generated by spf13/cobra on 25-May-2025

View File

@@ -25,4 +25,4 @@ hrp ios reboot [flags]
* [hrp ios](hrp_ios.md) - simple utils for ios device management
###### Auto generated by spf13/cobra on 17-May-2025
###### Auto generated by spf13/cobra on 25-May-2025

View File

@@ -24,4 +24,4 @@ hrp ios tunnel [flags]
* [hrp ios](hrp_ios.md) - simple utils for ios device management
###### Auto generated by spf13/cobra on 17-May-2025
###### Auto generated by spf13/cobra on 25-May-2025

View File

@@ -26,4 +26,4 @@ hrp ios uninstall [flags] PACKAGE
* [hrp ios](hrp_ios.md) - simple utils for ios device management
###### Auto generated by spf13/cobra on 17-May-2025
###### Auto generated by spf13/cobra on 25-May-2025

View File

@@ -28,4 +28,4 @@ hrp ios xctest [flags]
* [hrp ios](hrp_ios.md) - simple utils for ios device management
###### Auto generated by spf13/cobra on 17-May-2025
###### Auto generated by spf13/cobra on 25-May-2025

View File

@@ -0,0 +1,31 @@
## hrp mcp-server
Start MCP server for UI automation
### Synopsis
Start MCP server for UI automation, expose device driver via MCP protocol
```
hrp mcp-server [flags]
```
### Options
```
-h, --help help for mcp-server
```
### Options inherited from parent commands
```
--log-json set log to json format (default colorized console)
-l, --log-level string set log level (default "INFO")
--venv string specify python3 venv path
```
### SEE ALSO
* [hrp](hrp.md) - All-in-One Testing Framework for API, UI and Performance
###### Auto generated by spf13/cobra on 25-May-2025

View File

@@ -13,10 +13,10 @@ hrp mcphost [flags]
### Options
```
--dump string path to save the exported tools JSON file
-h, --help help for mcphost
-c, --mcp-config string path to the MCP config file (default "$HOME/.hrp/mcp.json")
--system-prompt string path to system prompt JSON file
--dump string path to save the exported tools JSON file
-h, --help help for mcphost
-c, --mcp-config string path to the MCP config file (default "$HOME/.hrp/mcp.json")
--with-uixt start built-in uixt MCP server
```
### Options inherited from parent commands
@@ -31,4 +31,4 @@ hrp mcphost [flags]
* [hrp](hrp.md) - All-in-One Testing Framework for API, UI and Performance
###### Auto generated by spf13/cobra on 17-May-2025
###### Auto generated by spf13/cobra on 25-May-2025

View File

@@ -24,4 +24,4 @@ hrp pytest $path ... [flags]
* [hrp](hrp.md) - All-in-One Testing Framework for API, UI and Performance
###### Auto generated by spf13/cobra on 17-May-2025
###### Auto generated by spf13/cobra on 25-May-2025

View File

@@ -44,4 +44,4 @@ hrp run $path... [flags]
* [hrp](hrp.md) - All-in-One Testing Framework for API, UI and Performance
###### Auto generated by spf13/cobra on 17-May-2025
###### Auto generated by spf13/cobra on 25-May-2025

View File

@@ -30,4 +30,4 @@ hrp server start [flags]
* [hrp](hrp.md) - All-in-One Testing Framework for API, UI and Performance
###### Auto generated by spf13/cobra on 17-May-2025
###### Auto generated by spf13/cobra on 25-May-2025

View File

@@ -29,4 +29,4 @@ hrp startproject $project_name [flags]
* [hrp](hrp.md) - All-in-One Testing Framework for API, UI and Performance
###### Auto generated by spf13/cobra on 17-May-2025
###### Auto generated by spf13/cobra on 25-May-2025

View File

@@ -24,4 +24,4 @@ hrp wiki [flags]
* [hrp](hrp.md) - All-in-One Testing Framework for API, UI and Performance
###### Auto generated by spf13/cobra on 17-May-2025
###### Auto generated by spf13/cobra on 25-May-2025

View File

@@ -1 +1 @@
v5.0.0-beta-2505250810
v5.0.0-beta-2505252353

View File

@@ -212,7 +212,7 @@ func (s *StepMobile) Back() *StepMobile {
// Swipe drags from [sx, sy] to [ex, ey]
func (s *StepMobile) Swipe(sx, sy, ex, ey float64, opts ...option.ActionOption) *StepMobile {
action := uixt.MobileAction{
Method: option.ACTION_Swipe,
Method: option.ACTION_SwipeCoordinate,
Params: []float64{sx, sy, ex, ey},
Options: option.NewActionOptions(opts...),
}
@@ -223,7 +223,7 @@ func (s *StepMobile) Swipe(sx, sy, ex, ey float64, opts ...option.ActionOption)
func (s *StepMobile) SwipeUp(opts ...option.ActionOption) *StepMobile {
action := uixt.MobileAction{
Method: option.ACTION_Swipe,
Method: option.ACTION_SwipeDirection,
Params: "up",
Options: option.NewActionOptions(opts...),
}
@@ -234,7 +234,7 @@ func (s *StepMobile) SwipeUp(opts ...option.ActionOption) *StepMobile {
func (s *StepMobile) SwipeDown(opts ...option.ActionOption) *StepMobile {
action := uixt.MobileAction{
Method: option.ACTION_Swipe,
Method: option.ACTION_SwipeDirection,
Params: "down",
Options: option.NewActionOptions(opts...),
}
@@ -245,7 +245,7 @@ func (s *StepMobile) SwipeDown(opts ...option.ActionOption) *StepMobile {
func (s *StepMobile) SwipeLeft(opts ...option.ActionOption) *StepMobile {
action := uixt.MobileAction{
Method: option.ACTION_Swipe,
Method: option.ACTION_SwipeDirection,
Params: "left",
Options: option.NewActionOptions(opts...),
}
@@ -256,7 +256,7 @@ func (s *StepMobile) SwipeLeft(opts ...option.ActionOption) *StepMobile {
func (s *StepMobile) SwipeRight(opts ...option.ActionOption) *StepMobile {
action := uixt.MobileAction{
Method: option.ACTION_Swipe,
Method: option.ACTION_SwipeDirection,
Params: "right",
Options: option.NewActionOptions(opts...),
}

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