mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-11 18:11:21 +08:00
959 lines
33 KiB
Go
959 lines
33 KiB
Go
package uixt
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"slices"
|
|
|
|
"github.com/mark3labs/mcp-go/mcp"
|
|
"github.com/mark3labs/mcp-go/server"
|
|
"github.com/rs/zerolog/log"
|
|
|
|
"github.com/httprunner/httprunner/v5/internal/builtin"
|
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
|
)
|
|
|
|
// 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 {
|
|
// 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
|
|
}
|
|
|
|
func (t *ToolSwipe) Description() string {
|
|
return "Swipe on the screen by direction (up/down/left/right) or coordinates [fromX, fromY, toX, toY]"
|
|
}
|
|
|
|
func (t *ToolSwipe) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.ActionOptions{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_Swipe)
|
|
}
|
|
|
|
func (t *ToolSwipe) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
// Check if it's direction-based swipe (has "direction" parameter)
|
|
if _, exists := request.GetArguments()["direction"]; exists {
|
|
// Delegate to ToolSwipeDirection
|
|
directionTool := &ToolSwipeDirection{}
|
|
return directionTool.Implement()(ctx, request)
|
|
} else {
|
|
// Delegate to ToolSwipeCoordinate
|
|
coordinateTool := &ToolSwipeCoordinate{}
|
|
return coordinateTool.Implement()(ctx, request)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (t *ToolSwipe) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) {
|
|
// Check if params is a string (direction-based swipe)
|
|
if _, ok := action.Params.(string); ok {
|
|
// Delegate to ToolSwipeDirection but use our tool name
|
|
directionTool := &ToolSwipeDirection{}
|
|
request, err := directionTool.ConvertActionToCallToolRequest(action)
|
|
if err != nil {
|
|
return mcp.CallToolRequest{}, err
|
|
}
|
|
// Change the tool name to use generic swipe
|
|
request.Params.Name = string(t.Name())
|
|
return request, nil
|
|
}
|
|
|
|
// Check if params is a coordinate array (coordinate-based swipe)
|
|
if paramSlice, err := builtin.ConvertToFloat64Slice(action.Params); err == nil && len(paramSlice) == 4 {
|
|
// Delegate to ToolSwipeCoordinate but use our tool name
|
|
coordinateTool := &ToolSwipeCoordinate{}
|
|
request, err := coordinateTool.ConvertActionToCallToolRequest(action)
|
|
if err != nil {
|
|
return mcp.CallToolRequest{}, err
|
|
}
|
|
// Change the tool name to use generic swipe
|
|
request.Params.Name = string(t.Name())
|
|
return request, nil
|
|
}
|
|
|
|
return mcp.CallToolRequest{}, fmt.Errorf("invalid swipe params: %v, expected string direction or [fromX, fromY, toX, toY] coordinates", action.Params)
|
|
}
|
|
|
|
// ToolSwipeDirection implements the swipe_direction tool call.
|
|
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
|
|
}
|
|
|
|
func (t *ToolSwipeDirection) Description() string {
|
|
return "Swipe on the screen in a specific direction (up, down, left, right)"
|
|
}
|
|
|
|
func (t *ToolSwipeDirection) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.ActionOptions{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_SwipeDirection)
|
|
}
|
|
|
|
func (t *ToolSwipeDirection) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
arguments := request.GetArguments()
|
|
driverExt, err := setupXTDriver(ctx, arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
unifiedReq, err := parseActionOptions(arguments)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
swipeDirection := unifiedReq.Direction.(string)
|
|
|
|
// Swipe action logic
|
|
log.Info().Str("direction", swipeDirection).Msg("performing swipe")
|
|
|
|
// Validate direction
|
|
validDirections := []string{"up", "down", "left", "right"}
|
|
if !slices.Contains(validDirections, swipeDirection) {
|
|
return nil, fmt.Errorf("invalid swipe direction: %s, expected one of: %v",
|
|
swipeDirection, validDirections)
|
|
}
|
|
|
|
// Build all options from request arguments
|
|
opts := unifiedReq.Options()
|
|
if unifiedReq.Duration == 0 {
|
|
opts = append(opts, option.WithDuration(0.5))
|
|
}
|
|
if unifiedReq.PressDuration == 0 {
|
|
opts = append(opts, option.WithPressDuration(0.1))
|
|
}
|
|
|
|
// Convert direction to coordinates and perform swipe
|
|
var fromX, fromY, toX, toY float64
|
|
switch swipeDirection {
|
|
case "up":
|
|
fromX, fromY, toX, toY = 0.5, 0.5, 0.5, 0.1
|
|
err = driverExt.Swipe(fromX, fromY, toX, toY, opts...)
|
|
case "down":
|
|
fromX, fromY, toX, toY = 0.5, 0.5, 0.5, 0.9
|
|
err = driverExt.Swipe(fromX, fromY, toX, toY, opts...)
|
|
case "left":
|
|
fromX, fromY, toX, toY = 0.5, 0.5, 0.1, 0.5
|
|
err = driverExt.Swipe(fromX, fromY, toX, toY, opts...)
|
|
case "right":
|
|
fromX, fromY, toX, toY = 0.5, 0.5, 0.9, 0.5
|
|
err = driverExt.Swipe(fromX, fromY, toX, toY, opts...)
|
|
default:
|
|
return NewMCPErrorResponse(fmt.Sprintf("Unexpected swipe direction: %s", swipeDirection)), nil
|
|
}
|
|
|
|
if err != nil {
|
|
return NewMCPErrorResponse(fmt.Sprintf("Swipe failed: %s", err.Error())), err
|
|
}
|
|
|
|
message := fmt.Sprintf("Successfully swiped %s", swipeDirection)
|
|
returnData := ToolSwipeDirection{
|
|
Direction: swipeDirection,
|
|
FromX: fromX,
|
|
FromY: fromY,
|
|
ToX: toX,
|
|
ToY: toY,
|
|
}
|
|
|
|
return NewMCPSuccessResponse(message, &returnData), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolSwipeDirection) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) {
|
|
// Handle direction swipe like "up", "down", "left", "right"
|
|
if direction, ok := action.Params.(string); ok {
|
|
arguments := map[string]any{
|
|
"direction": direction,
|
|
}
|
|
// 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 BuildMCPCallToolRequest(t.Name(), arguments, action), nil
|
|
}
|
|
return mcp.CallToolRequest{}, fmt.Errorf("invalid swipe params: %v", action.Params)
|
|
}
|
|
|
|
// ToolSwipeCoordinate implements the swipe_coordinate tool call.
|
|
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
|
|
}
|
|
|
|
func (t *ToolSwipeCoordinate) Description() string {
|
|
return "Perform swipe with specific start and end coordinates and custom timing"
|
|
}
|
|
|
|
func (t *ToolSwipeCoordinate) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.ActionOptions{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_SwipeCoordinate)
|
|
}
|
|
|
|
func (t *ToolSwipeCoordinate) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
arguments := request.GetArguments()
|
|
driverExt, err := setupXTDriver(ctx, arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
unifiedReq, err := parseActionOptions(arguments)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Validate required parameters
|
|
if unifiedReq.FromX == 0 || unifiedReq.FromY == 0 || unifiedReq.ToX == 0 || unifiedReq.ToY == 0 {
|
|
return nil, fmt.Errorf("fromX, fromY, toX, and toY coordinates are required")
|
|
}
|
|
|
|
// Advanced swipe action logic using prepareSwipeAction like the original DoAction
|
|
log.Info().
|
|
Float64("fromX", unifiedReq.FromX).Float64("fromY", unifiedReq.FromY).
|
|
Float64("toX", unifiedReq.ToX).Float64("toY", unifiedReq.ToY).
|
|
Msg("performing advanced swipe")
|
|
|
|
params := []float64{unifiedReq.FromX, unifiedReq.FromY, unifiedReq.ToX, unifiedReq.ToY}
|
|
|
|
// Build all options from request arguments
|
|
opts := unifiedReq.Options()
|
|
|
|
swipeAction := prepareSwipeAction(driverExt, params, opts...)
|
|
err = swipeAction(driverExt)
|
|
if err != nil {
|
|
return NewMCPErrorResponse(fmt.Sprintf("Advanced swipe failed: %s", err.Error())), err
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
func (t *ToolSwipeCoordinate) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) {
|
|
if paramSlice, err := builtin.ConvertToFloat64Slice(action.Params); err == nil && len(paramSlice) == 4 {
|
|
arguments := map[string]any{
|
|
"from_x": paramSlice[0],
|
|
"from_y": paramSlice[1],
|
|
"to_x": paramSlice[2],
|
|
"to_y": 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 BuildMCPCallToolRequest(t.Name(), arguments, action), nil
|
|
}
|
|
return mcp.CallToolRequest{}, fmt.Errorf("invalid swipe advanced params: %v", action.Params)
|
|
}
|
|
|
|
// ToolSwipeToTapApp implements the swipe_to_tap_app tool call.
|
|
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
|
|
}
|
|
|
|
func (t *ToolSwipeToTapApp) Description() string {
|
|
return "Swipe to find and tap an app by name"
|
|
}
|
|
|
|
func (t *ToolSwipeToTapApp) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.ActionOptions{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_SwipeToTapApp)
|
|
}
|
|
|
|
func (t *ToolSwipeToTapApp) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
arguments := request.GetArguments()
|
|
driverExt, err := setupXTDriver(ctx, arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
unifiedReq, err := parseActionOptions(arguments)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Build action options from request structure
|
|
opts := unifiedReq.Options()
|
|
|
|
// Swipe to tap app action logic
|
|
err = driverExt.SwipeToTapApp(unifiedReq.AppName, opts...)
|
|
if err != nil {
|
|
return NewMCPErrorResponse(fmt.Sprintf("Swipe to tap app failed: %s", err.Error())), err
|
|
}
|
|
|
|
message := fmt.Sprintf("Successfully found and tapped app: %s", unifiedReq.AppName)
|
|
returnData := ToolSwipeToTapApp{AppName: unifiedReq.AppName}
|
|
|
|
return NewMCPSuccessResponse(message, &returnData), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolSwipeToTapApp) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) {
|
|
if appName, ok := action.Params.(string); ok {
|
|
arguments := map[string]any{
|
|
"appName": appName,
|
|
}
|
|
return BuildMCPCallToolRequest(t.Name(), arguments, action), nil
|
|
}
|
|
return mcp.CallToolRequest{}, fmt.Errorf("invalid swipe to tap app params: %v", action.Params)
|
|
}
|
|
|
|
// ToolSwipeToTapText implements the swipe_to_tap_text tool call.
|
|
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
|
|
}
|
|
|
|
func (t *ToolSwipeToTapText) Description() string {
|
|
return "Swipe to find and tap text on screen"
|
|
}
|
|
|
|
func (t *ToolSwipeToTapText) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.ActionOptions{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_SwipeToTapText)
|
|
}
|
|
|
|
func (t *ToolSwipeToTapText) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
arguments := request.GetArguments()
|
|
driverExt, err := setupXTDriver(ctx, arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
unifiedReq, err := parseActionOptions(arguments)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Build all options from request arguments
|
|
opts := unifiedReq.Options()
|
|
|
|
// Swipe to tap text action logic
|
|
err = driverExt.SwipeToTapTexts([]string{unifiedReq.Text}, opts...)
|
|
if err != nil {
|
|
return NewMCPErrorResponse(fmt.Sprintf("Swipe to tap text failed: %s", err.Error())), err
|
|
}
|
|
|
|
message := fmt.Sprintf("Successfully found and tapped text: %s", unifiedReq.Text)
|
|
returnData := ToolSwipeToTapText{Text: unifiedReq.Text}
|
|
|
|
return NewMCPSuccessResponse(message, &returnData), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolSwipeToTapText) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) {
|
|
if text, ok := action.Params.(string); ok {
|
|
arguments := map[string]any{
|
|
"text": text,
|
|
}
|
|
return BuildMCPCallToolRequest(t.Name(), arguments, action), nil
|
|
}
|
|
return mcp.CallToolRequest{}, fmt.Errorf("invalid swipe to tap text params: %v", action.Params)
|
|
}
|
|
|
|
// ToolSwipeToTapTexts implements the swipe_to_tap_texts tool call.
|
|
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
|
|
}
|
|
|
|
func (t *ToolSwipeToTapTexts) Description() string {
|
|
return "Swipe to find and tap one of multiple texts on screen"
|
|
}
|
|
|
|
func (t *ToolSwipeToTapTexts) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.ActionOptions{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_SwipeToTapTexts)
|
|
}
|
|
|
|
func (t *ToolSwipeToTapTexts) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
arguments := request.GetArguments()
|
|
driverExt, err := setupXTDriver(ctx, arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
unifiedReq, err := parseActionOptions(arguments)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Build all options from request arguments
|
|
opts := unifiedReq.Options()
|
|
|
|
// Swipe to tap texts action logic
|
|
err = driverExt.SwipeToTapTexts(unifiedReq.Texts, opts...)
|
|
if err != nil {
|
|
return NewMCPErrorResponse(fmt.Sprintf("Swipe to tap texts failed: %s", err.Error())), err
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
func (t *ToolSwipeToTapTexts) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) {
|
|
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]any{
|
|
"texts": texts,
|
|
}
|
|
return BuildMCPCallToolRequest(t.Name(), arguments, action), nil
|
|
}
|
|
|
|
// ToolDrag implements the drag tool call.
|
|
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
|
|
}
|
|
|
|
func (t *ToolDrag) Description() string {
|
|
return "Drag from one point to another on the mobile device screen"
|
|
}
|
|
|
|
func (t *ToolDrag) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.ActionOptions{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_Drag)
|
|
}
|
|
|
|
func (t *ToolDrag) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
arguments := request.GetArguments()
|
|
driverExt, err := setupXTDriver(ctx, arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
unifiedReq, err := parseActionOptions(arguments)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Validate required parameters - check if coordinates are provided (not just non-zero)
|
|
_, hasFromX := arguments["from_x"]
|
|
_, hasFromY := arguments["from_y"]
|
|
_, hasToX := arguments["to_x"]
|
|
_, hasToY := arguments["to_y"]
|
|
if !hasFromX || !hasFromY || !hasToX || !hasToY {
|
|
return nil, fmt.Errorf("from_x, from_y, to_x, and to_y coordinates are required")
|
|
}
|
|
|
|
// Build all options from request arguments
|
|
opts := unifiedReq.Options()
|
|
if unifiedReq.Duration == 0 {
|
|
opts = append(opts, option.WithDuration(0.5))
|
|
}
|
|
|
|
// Drag action logic
|
|
err = driverExt.Swipe(unifiedReq.FromX, unifiedReq.FromY, unifiedReq.ToX, unifiedReq.ToY, opts...)
|
|
if err != nil {
|
|
return NewMCPErrorResponse(fmt.Sprintf("Drag failed: %s", err.Error())), err
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
func (t *ToolDrag) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) {
|
|
if paramSlice, err := builtin.ConvertToFloat64Slice(action.Params); err == nil && len(paramSlice) == 4 {
|
|
arguments := map[string]any{
|
|
"from_x": paramSlice[0],
|
|
"from_y": paramSlice[1],
|
|
"to_x": paramSlice[2],
|
|
"to_y": paramSlice[3],
|
|
}
|
|
// Add duration from options
|
|
if duration := action.ActionOptions.Duration; duration > 0 {
|
|
arguments["duration"] = duration * 1000 // convert to milliseconds
|
|
}
|
|
return BuildMCPCallToolRequest(t.Name(), arguments, action), nil
|
|
}
|
|
return mcp.CallToolRequest{}, fmt.Errorf("invalid drag parameters: %v", action.Params)
|
|
}
|
|
|
|
// ToolSIMSwipeDirection implements the sim_swipe_direction tool call.
|
|
type ToolSIMSwipeDirection 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)"`
|
|
StartX float64 `json:"startX" desc:"Starting X coordinate of the simulated swipe"`
|
|
StartY float64 `json:"startY" desc:"Starting Y coordinate of the simulated swipe"`
|
|
MinDistance float64 `json:"minDistance" desc:"Minimum distance of the simulated swipe"`
|
|
MaxDistance float64 `json:"maxDistance" desc:"Maximum distance of the simulated swipe"`
|
|
ActualDistance float64 `json:"actualDistance" desc:"Actual distance of the simulated swipe"`
|
|
}
|
|
|
|
func (t *ToolSIMSwipeDirection) Name() option.ActionName {
|
|
return option.ACTION_SIMSwipeDirection
|
|
}
|
|
|
|
func (t *ToolSIMSwipeDirection) Description() string {
|
|
return "Perform simulated swipe in specified direction with random distance and human-like touch patterns"
|
|
}
|
|
|
|
func (t *ToolSIMSwipeDirection) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.ActionOptions{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_SIMSwipeDirection)
|
|
}
|
|
|
|
func (t *ToolSIMSwipeDirection) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
arguments := request.GetArguments()
|
|
driverExt, err := setupXTDriver(ctx, arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
unifiedReq, err := parseActionOptions(arguments)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Validate required parameters
|
|
if unifiedReq.Direction == nil {
|
|
return nil, fmt.Errorf("direction parameter is required")
|
|
}
|
|
direction, ok := unifiedReq.Direction.(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("direction must be a string")
|
|
}
|
|
|
|
// Validate direction
|
|
validDirections := []string{"up", "down", "left", "right"}
|
|
if !slices.Contains(validDirections, direction) {
|
|
return nil, fmt.Errorf("invalid swipe direction: %s, expected one of: %v",
|
|
direction, validDirections)
|
|
}
|
|
|
|
// Default values if not provided - use fromX/fromY instead of startX/startY
|
|
fromX := unifiedReq.FromX
|
|
fromY := unifiedReq.FromY
|
|
simMinDistance := unifiedReq.SIMMinDistance
|
|
simMaxDistance := unifiedReq.SIMMaxDistance
|
|
|
|
if fromX == 0 {
|
|
fromX = 0.5 // default to center
|
|
}
|
|
if fromY == 0 {
|
|
fromY = 0.5 // default to center
|
|
}
|
|
if simMinDistance == 0 {
|
|
simMinDistance = 100 // default minimum distance
|
|
}
|
|
if simMaxDistance == 0 {
|
|
simMaxDistance = 300 // default maximum distance
|
|
}
|
|
|
|
log.Info().
|
|
Str("direction", direction).
|
|
Float64("startX", fromX).
|
|
Float64("startY", fromY).
|
|
Float64("minDistance", simMinDistance).
|
|
Float64("maxDistance", simMaxDistance).
|
|
Msg("performing simulated swipe with direction")
|
|
|
|
// Build all options from request arguments
|
|
opts := unifiedReq.Options()
|
|
|
|
// Call the underlying SIMSwipeWithDirection method (check if driver supports SIM)
|
|
if simDriver, ok := driverExt.IDriver.(SIMSupport); ok {
|
|
err = simDriver.SIMSwipeWithDirection(direction, fromX, fromY, simMinDistance, simMaxDistance, opts...)
|
|
if err != nil {
|
|
return NewMCPErrorResponse(fmt.Sprintf("Simulated swipe failed: %s", err.Error())), err
|
|
}
|
|
} else {
|
|
return NewMCPErrorResponse("SIMSwipeWithDirection is not supported by the current driver"), fmt.Errorf("driver does not implement SIMSupport interface")
|
|
}
|
|
|
|
// Calculate actual distance for response (approximate)
|
|
actualDistance := simMinDistance
|
|
if simMaxDistance > simMinDistance {
|
|
actualDistance = simMinDistance + (simMaxDistance-simMinDistance)*0.5 // approximate middle value
|
|
}
|
|
|
|
message := fmt.Sprintf("Successfully performed simulated swipe %s from (%.2f, %.2f) with distance %.2f",
|
|
direction, fromX, fromY, actualDistance)
|
|
returnData := ToolSIMSwipeDirection{
|
|
Direction: direction,
|
|
StartX: fromX,
|
|
StartY: fromY,
|
|
MinDistance: simMinDistance,
|
|
MaxDistance: simMaxDistance,
|
|
ActualDistance: actualDistance,
|
|
}
|
|
|
|
return NewMCPSuccessResponse(message, &returnData), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolSIMSwipeDirection) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) {
|
|
// Handle params as map[string]interface{}
|
|
if paramsMap, ok := action.Params.(map[string]interface{}); ok {
|
|
arguments := map[string]any{}
|
|
|
|
// Extract direction
|
|
if direction, exists := paramsMap["direction"]; exists {
|
|
arguments["direction"] = direction
|
|
}
|
|
|
|
// Extract coordinates and distances - use new field names directly
|
|
if fromX, exists := paramsMap["from_x"]; exists {
|
|
arguments["from_x"] = fromX
|
|
}
|
|
if fromY, exists := paramsMap["from_y"]; exists {
|
|
arguments["from_y"] = fromY
|
|
}
|
|
if minDistance, exists := paramsMap["sim_min_distance"]; exists {
|
|
arguments["sim_min_distance"] = minDistance
|
|
}
|
|
if maxDistance, exists := paramsMap["sim_max_distance"]; exists {
|
|
arguments["sim_max_distance"] = maxDistance
|
|
}
|
|
|
|
// 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 BuildMCPCallToolRequest(t.Name(), arguments, action), nil
|
|
}
|
|
return mcp.CallToolRequest{}, fmt.Errorf("invalid SIM swipe direction params: %v", action.Params)
|
|
}
|
|
|
|
// ToolSIMSwipeInArea implements the sim_swipe_in_area tool call.
|
|
type ToolSIMSwipeInArea 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)"`
|
|
AreaStartX float64 `json:"areaStartX" desc:"Area starting X coordinate"`
|
|
AreaStartY float64 `json:"areaStartY" desc:"Area starting Y coordinate"`
|
|
AreaEndX float64 `json:"areaEndX" desc:"Area ending X coordinate"`
|
|
AreaEndY float64 `json:"areaEndY" desc:"Area ending Y coordinate"`
|
|
MinDistance float64 `json:"minDistance" desc:"Minimum distance of the simulated swipe"`
|
|
MaxDistance float64 `json:"maxDistance" desc:"Maximum distance of the simulated swipe"`
|
|
}
|
|
|
|
func (t *ToolSIMSwipeInArea) Name() option.ActionName {
|
|
return option.ACTION_SIMSwipeInArea
|
|
}
|
|
|
|
func (t *ToolSIMSwipeInArea) Description() string {
|
|
return "Perform simulated swipe in specified area with direction and random distance"
|
|
}
|
|
|
|
func (t *ToolSIMSwipeInArea) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.ActionOptions{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_SIMSwipeInArea)
|
|
}
|
|
|
|
func (t *ToolSIMSwipeInArea) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
arguments := request.GetArguments()
|
|
driverExt, err := setupXTDriver(ctx, arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
unifiedReq, err := parseActionOptions(arguments)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Validate required parameters
|
|
if unifiedReq.Direction == nil {
|
|
return nil, fmt.Errorf("direction parameter is required")
|
|
}
|
|
direction, ok := unifiedReq.Direction.(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("direction must be a string")
|
|
}
|
|
|
|
// Validate direction
|
|
validDirections := []string{"up", "down", "left", "right"}
|
|
if !slices.Contains(validDirections, direction) {
|
|
return nil, fmt.Errorf("invalid swipe direction: %s, expected one of: %v",
|
|
direction, validDirections)
|
|
}
|
|
|
|
// Get area coordinates - use SIM-prefixed fields
|
|
simAreaStartX := unifiedReq.SIMAreaStartX
|
|
simAreaStartY := unifiedReq.SIMAreaStartY
|
|
simAreaEndX := unifiedReq.SIMAreaEndX
|
|
simAreaEndY := unifiedReq.SIMAreaEndY
|
|
simMinDistance := unifiedReq.SIMMinDistance
|
|
simMaxDistance := unifiedReq.SIMMaxDistance
|
|
|
|
// Default values
|
|
if simMinDistance == 0 {
|
|
simMinDistance = 100
|
|
}
|
|
if simMaxDistance == 0 {
|
|
simMaxDistance = 300
|
|
}
|
|
|
|
log.Info().
|
|
Str("direction", direction).
|
|
Float64("areaStartX", simAreaStartX).
|
|
Float64("areaStartY", simAreaStartY).
|
|
Float64("areaEndX", simAreaEndX).
|
|
Float64("areaEndY", simAreaEndY).
|
|
Float64("minDistance", simMinDistance).
|
|
Float64("maxDistance", simMaxDistance).
|
|
Msg("performing simulated swipe in area")
|
|
|
|
// Build all options from request arguments
|
|
opts := unifiedReq.Options()
|
|
|
|
// Call the underlying SIMSwipeInArea method (check if driver supports SIM)
|
|
if simDriver, ok := driverExt.IDriver.(SIMSupport); ok {
|
|
err = simDriver.SIMSwipeInArea(direction, simAreaStartX, simAreaStartY, simAreaEndX, simAreaEndY, simMinDistance, simMaxDistance, opts...)
|
|
if err != nil {
|
|
return NewMCPErrorResponse(fmt.Sprintf("Simulated swipe in area failed: %s", err.Error())), err
|
|
}
|
|
} else {
|
|
return NewMCPErrorResponse("SIMSwipeInArea is not supported by the current driver"), fmt.Errorf("driver does not implement SIMSupport interface")
|
|
}
|
|
|
|
message := fmt.Sprintf("Successfully performed simulated swipe %s in area (%.2f,%.2f)-(%.2f,%.2f)",
|
|
direction, simAreaStartX, simAreaStartY, simAreaEndX, simAreaEndY)
|
|
returnData := ToolSIMSwipeInArea{
|
|
Direction: direction,
|
|
AreaStartX: simAreaStartX,
|
|
AreaStartY: simAreaStartY,
|
|
AreaEndX: simAreaEndX,
|
|
AreaEndY: simAreaEndY,
|
|
MinDistance: simMinDistance,
|
|
MaxDistance: simMaxDistance,
|
|
}
|
|
|
|
return NewMCPSuccessResponse(message, &returnData), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolSIMSwipeInArea) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) {
|
|
// Handle params as map[string]interface{}
|
|
if paramsMap, ok := action.Params.(map[string]interface{}); ok {
|
|
arguments := map[string]any{}
|
|
|
|
// Extract direction
|
|
if direction, exists := paramsMap["direction"]; exists {
|
|
arguments["direction"] = direction
|
|
}
|
|
|
|
// Extract area coordinates and distances - use SIM-prefixed field names
|
|
if areaStartX, exists := paramsMap["sim_area_start_x"]; exists {
|
|
arguments["sim_area_start_x"] = areaStartX
|
|
}
|
|
if areaStartY, exists := paramsMap["sim_area_start_y"]; exists {
|
|
arguments["sim_area_start_y"] = areaStartY
|
|
}
|
|
if areaEndX, exists := paramsMap["sim_area_end_x"]; exists {
|
|
arguments["sim_area_end_x"] = areaEndX
|
|
}
|
|
if areaEndY, exists := paramsMap["sim_area_end_y"]; exists {
|
|
arguments["sim_area_end_y"] = areaEndY
|
|
}
|
|
if minDistance, exists := paramsMap["sim_min_distance"]; exists {
|
|
arguments["sim_min_distance"] = minDistance
|
|
}
|
|
if maxDistance, exists := paramsMap["sim_max_distance"]; exists {
|
|
arguments["sim_max_distance"] = maxDistance
|
|
}
|
|
|
|
// 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 BuildMCPCallToolRequest(t.Name(), arguments, action), nil
|
|
}
|
|
return mcp.CallToolRequest{}, fmt.Errorf("invalid SIM swipe in area params: %v", action.Params)
|
|
}
|
|
|
|
// ToolSIMSwipeFromPointToPoint implements the sim_swipe_point_to_point tool call.
|
|
type ToolSIMSwipeFromPointToPoint struct {
|
|
// Return data fields - these define the structure of data returned by this tool
|
|
StartX float64 `json:"startX" desc:"Starting X coordinate"`
|
|
StartY float64 `json:"startY" desc:"Starting Y coordinate"`
|
|
EndX float64 `json:"endX" desc:"Ending X coordinate"`
|
|
EndY float64 `json:"endY" desc:"Ending Y coordinate"`
|
|
}
|
|
|
|
func (t *ToolSIMSwipeFromPointToPoint) Name() option.ActionName {
|
|
return option.ACTION_SIMSwipeFromPointToPoint
|
|
}
|
|
|
|
func (t *ToolSIMSwipeFromPointToPoint) Description() string {
|
|
return "Perform simulated swipe from point to point with human-like touch patterns"
|
|
}
|
|
|
|
func (t *ToolSIMSwipeFromPointToPoint) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.ActionOptions{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_SIMSwipeFromPointToPoint)
|
|
}
|
|
|
|
func (t *ToolSIMSwipeFromPointToPoint) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
arguments := request.GetArguments()
|
|
driverExt, err := setupXTDriver(ctx, arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
unifiedReq, err := parseActionOptions(arguments)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Get coordinates from arguments - use fromX/fromY instead of startX/startY
|
|
fromX := unifiedReq.FromX
|
|
fromY := unifiedReq.FromY
|
|
toX := unifiedReq.ToX
|
|
toY := unifiedReq.ToY
|
|
|
|
log.Info().
|
|
Float64("startX", fromX).
|
|
Float64("startY", fromY).
|
|
Float64("endX", toX).
|
|
Float64("endY", toY).
|
|
Msg("performing simulated point to point swipe")
|
|
|
|
// Build all options from request arguments
|
|
opts := unifiedReq.Options()
|
|
|
|
// Call the underlying SIMSwipeFromPointToPoint method (check if driver supports SIM)
|
|
if simDriver, ok := driverExt.IDriver.(SIMSupport); ok {
|
|
err = simDriver.SIMSwipeFromPointToPoint(fromX, fromY, toX, toY, opts...)
|
|
if err != nil {
|
|
return NewMCPErrorResponse(fmt.Sprintf("Simulated point to point swipe failed: %s", err.Error())), err
|
|
}
|
|
} else {
|
|
return NewMCPErrorResponse("SIMSwipeFromPointToPoint is not supported by the current driver"), fmt.Errorf("driver does not implement SIMSupport interface")
|
|
}
|
|
|
|
message := fmt.Sprintf("Successfully performed simulated swipe from (%.2f,%.2f) to (%.2f,%.2f)",
|
|
fromX, fromY, toX, toY)
|
|
returnData := ToolSIMSwipeFromPointToPoint{
|
|
StartX: fromX,
|
|
StartY: fromY,
|
|
EndX: toX,
|
|
EndY: toY,
|
|
}
|
|
|
|
return NewMCPSuccessResponse(message, &returnData), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolSIMSwipeFromPointToPoint) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) {
|
|
// Handle params as map[string]interface{}
|
|
if paramsMap, ok := action.Params.(map[string]interface{}); ok {
|
|
arguments := map[string]any{}
|
|
|
|
// Extract coordinates - use new field names directly
|
|
if fromX, exists := paramsMap["from_x"]; exists {
|
|
arguments["from_x"] = fromX
|
|
}
|
|
if fromY, exists := paramsMap["from_y"]; exists {
|
|
arguments["from_y"] = fromY
|
|
}
|
|
if toX, exists := paramsMap["to_x"]; exists {
|
|
arguments["to_x"] = toX
|
|
}
|
|
if toY, exists := paramsMap["to_y"]; exists {
|
|
arguments["to_y"] = toY
|
|
}
|
|
|
|
// 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 BuildMCPCallToolRequest(t.Name(), arguments, action), nil
|
|
}
|
|
return mcp.CallToolRequest{}, fmt.Errorf("invalid SIM swipe point to point params: %v", action.Params)
|
|
}
|