mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-12 02:21:29 +08:00
- Migrated 39 remaining MCP tools from individual Request structures to UnifiedActionRequest
- All tools now use unifiedReq.GetMCPOptions(ACTION_*) instead of option.NewMCPOptions(Request{})
- Completed the unification of parameter definitions across all 40 MCP tools
- Eliminated duplication between ActionOptions and Request structures
- All tests pass, confirming successful migration
Tools migrated:
- Basic operations: TapAbsXY, TapByOCR, TapByCV, DoubleTapXY
- Device management: ListPackages, ScreenShot, GetScreenSize, PressButton
- App management: LaunchApp, TerminateApp, AppInstall, AppUninstall, AppClear
- Swipe operations: SwipeDirection, SwipeCoordinate, SwipeToTapApp, SwipeToTapText, SwipeToTapTexts
- Input/Navigation: Input, Home, Back, Drag
- Web operations: WebLoginNoneUI, SecondaryClick, HoverBySelector, TapBySelector, SecondaryClickBySelector, WebCloseTab
- System utilities: SetIme, GetSource, ClosePopups
- Sleep operations: SleepMS, SleepRandom
- AI/Task management: AIAction, Finished
This completes the ActionOptions and Request structures integration initiative.
2407 lines
79 KiB
Go
2407 lines
79 KiB
Go
package uixt
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/danielpaulus/go-ios/ios"
|
|
"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/internal/version"
|
|
"github.com/httprunner/httprunner/v5/pkg/gadb"
|
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
|
"github.com/httprunner/httprunner/v5/uixt/types"
|
|
)
|
|
|
|
// MCPServer4XTDriver provides MCP (Model Context Protocol) interface for XTDriver.
|
|
//
|
|
// This implementation adopts a pure ActionTool-style architecture where:
|
|
// - Each MCP tool is implemented as a struct that implements the ActionTool interface
|
|
// - Operation logic is directly embedded in each tool's Implement() method
|
|
// - No intermediate action methods or coupling between tools
|
|
// - Complete decoupling from the original large switch-case DoAction method
|
|
//
|
|
// Architecture:
|
|
// MCP Request -> ActionTool.Implement() -> Direct Driver Method Call
|
|
//
|
|
// Benefits:
|
|
// - True ActionTool interface consistency across all tools
|
|
// - Complete decoupling with no method interdependencies
|
|
// - Unified code organization in a single file
|
|
// - Simplified error handling and logging per tool
|
|
// - Easy extensibility for new features
|
|
|
|
// NewMCPServer creates a new MCP server for XTDriver and registers all tools.
|
|
func NewMCPServer() *MCPServer4XTDriver {
|
|
mcpServer := server.NewMCPServer(
|
|
"uixt",
|
|
version.GetVersionInfo(),
|
|
server.WithToolCapabilities(false),
|
|
)
|
|
s := &MCPServer4XTDriver{
|
|
mcpServer: mcpServer,
|
|
actionToolMap: make(map[option.ActionMethod]ActionTool),
|
|
}
|
|
s.registerTools()
|
|
return s
|
|
}
|
|
|
|
// MCPServer4XTDriver wraps a MCPServer to expose XTDriver functionality via MCP protocol.
|
|
type MCPServer4XTDriver struct {
|
|
mcpServer *server.MCPServer
|
|
mcpTools []mcp.Tool // tools list for uixt
|
|
actionToolMap map[option.ActionMethod]ActionTool // action method to tool mapping
|
|
}
|
|
|
|
// Start runs the MCP server (blocking).
|
|
func (s *MCPServer4XTDriver) Start() error {
|
|
log.Info().Msg("Starting HttpRunner UIXT MCP Server...")
|
|
return server.ServeStdio(s.mcpServer)
|
|
}
|
|
|
|
// ListTools returns all registered tools
|
|
func (s *MCPServer4XTDriver) ListTools() []mcp.Tool {
|
|
return s.mcpTools
|
|
}
|
|
|
|
// GetTool returns a pointer to the mcp.Tool with the given name
|
|
func (s *MCPServer4XTDriver) GetTool(name string) *mcp.Tool {
|
|
for i := range s.mcpTools {
|
|
if s.mcpTools[i].Name == name {
|
|
return &s.mcpTools[i]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetToolByAction returns the tool that handles the given action method
|
|
func (s *MCPServer4XTDriver) GetToolByAction(actionMethod option.ActionMethod) ActionTool {
|
|
if s.actionToolMap == nil {
|
|
return nil
|
|
}
|
|
return s.actionToolMap[actionMethod]
|
|
}
|
|
|
|
// registerTools registers all MCP tools.
|
|
func (s *MCPServer4XTDriver) registerTools() {
|
|
// Device Tool
|
|
s.registerTool(&ToolListAvailableDevices{}) // ListAvailableDevices
|
|
s.registerTool(&ToolSelectDevice{}) // SelectDevice
|
|
|
|
// Tap Tools
|
|
s.registerTool(&ToolTapXY{}) // tap xy
|
|
s.registerTool(&ToolTapAbsXY{}) // tap abs xy
|
|
s.registerTool(&ToolTapByOCR{}) // tap by OCR
|
|
s.registerTool(&ToolTapByCV{}) // tap by CV
|
|
s.registerTool(&ToolDoubleTapXY{}) // double tap xy
|
|
|
|
// Swipe Tool
|
|
s.registerTool(&ToolSwipe{}) // generic swipe, auto-detect direction or coordinate
|
|
s.registerTool(&ToolSwipeDirection{}) // swipe direction, up/down/left/right
|
|
s.registerTool(&ToolSwipeCoordinate{}) // swipe coordinate, [fromX, fromY, toX, toY]
|
|
s.registerTool(&ToolSwipeToTapApp{})
|
|
s.registerTool(&ToolSwipeToTapText{})
|
|
s.registerTool(&ToolSwipeToTapTexts{})
|
|
|
|
// Drag Tool
|
|
s.registerTool(&ToolDrag{})
|
|
|
|
// Input Tool
|
|
s.registerTool(&ToolInput{})
|
|
|
|
// ScreenShot Tool
|
|
s.registerTool(&ToolScreenShot{})
|
|
|
|
// GetScreenSize Tool
|
|
s.registerTool(&ToolGetScreenSize{})
|
|
|
|
// PressButton Tool
|
|
s.registerTool(&ToolPressButton{})
|
|
s.registerTool(&ToolHome{}) // Home
|
|
s.registerTool(&ToolBack{}) // Back
|
|
|
|
// App actions
|
|
s.registerTool(&ToolListPackages{}) // ListPackages
|
|
s.registerTool(&ToolLaunchApp{}) // LaunchApp
|
|
s.registerTool(&ToolTerminateApp{}) // TerminateApp
|
|
s.registerTool(&ToolAppInstall{}) // AppInstall
|
|
s.registerTool(&ToolAppUninstall{}) // AppUninstall
|
|
s.registerTool(&ToolAppClear{}) // AppClear
|
|
|
|
// Sleep Tool
|
|
s.registerTool(&ToolSleep{})
|
|
s.registerTool(&ToolSleepMS{})
|
|
s.registerTool(&ToolSleepRandom{})
|
|
|
|
// Utils tools
|
|
s.registerTool(&ToolSetIme{})
|
|
s.registerTool(&ToolGetSource{})
|
|
s.registerTool(&ToolClosePopups{})
|
|
|
|
// PC/Web actions
|
|
s.registerTool(&ToolWebLoginNoneUI{})
|
|
s.registerTool(&ToolSecondaryClick{})
|
|
s.registerTool(&ToolHoverBySelector{})
|
|
s.registerTool(&ToolTapBySelector{})
|
|
s.registerTool(&ToolSecondaryClickBySelector{})
|
|
s.registerTool(&ToolWebCloseTab{})
|
|
|
|
// LLM actions
|
|
s.registerTool(&ToolAIAction{})
|
|
s.registerTool(&ToolFinished{})
|
|
}
|
|
|
|
func (s *MCPServer4XTDriver) registerTool(tool ActionTool) {
|
|
options := []mcp.ToolOption{
|
|
mcp.WithDescription(tool.Description()),
|
|
}
|
|
options = append(options, tool.Options()...)
|
|
|
|
toolName := string(tool.Name())
|
|
mcpTool := mcp.NewTool(toolName, options...)
|
|
s.mcpServer.AddTool(mcpTool, tool.Implement())
|
|
|
|
s.mcpTools = append(s.mcpTools, mcpTool)
|
|
s.actionToolMap[tool.Name()] = tool
|
|
|
|
log.Debug().Str("name", toolName).Str("type", toolName).Msg("register tool")
|
|
}
|
|
|
|
// ActionTool interface defines the contract for MCP tools
|
|
type ActionTool interface {
|
|
Name() option.ActionMethod
|
|
Description() string
|
|
Options() []mcp.ToolOption
|
|
Implement() server.ToolHandlerFunc
|
|
// ConvertActionToCallToolRequest converts MobileAction to mcp.CallToolRequest
|
|
ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error)
|
|
}
|
|
|
|
// buildMCPCallToolRequest is a helper function to build mcp.CallToolRequest
|
|
func buildMCPCallToolRequest(toolName option.ActionMethod, arguments map[string]any) mcp.CallToolRequest {
|
|
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: string(toolName),
|
|
Arguments: arguments,
|
|
},
|
|
}
|
|
}
|
|
|
|
// ToolListAvailableDevices implements the list_available_devices tool call.
|
|
type ToolListAvailableDevices struct{}
|
|
|
|
func (t *ToolListAvailableDevices) Name() option.ActionMethod {
|
|
return option.ACTION_ListAvailableDevices
|
|
}
|
|
|
|
func (t *ToolListAvailableDevices) Description() string {
|
|
return "List all available devices. If there are more than one device returned, you need to let the user select one of them."
|
|
}
|
|
|
|
func (t *ToolListAvailableDevices) Options() []mcp.ToolOption {
|
|
return []mcp.ToolOption{}
|
|
}
|
|
|
|
func (t *ToolListAvailableDevices) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
deviceList := make(map[string][]string)
|
|
if client, err := gadb.NewClient(); err == nil {
|
|
if androidDevices, err := client.DeviceList(); err == nil {
|
|
serialList := make([]string, 0, len(androidDevices))
|
|
for _, device := range androidDevices {
|
|
serialList = append(serialList, device.Serial())
|
|
}
|
|
deviceList["androidDevices"] = serialList
|
|
}
|
|
}
|
|
if iosDevices, err := ios.ListDevices(); err == nil {
|
|
serialList := make([]string, 0, len(iosDevices.DeviceList))
|
|
for _, dev := range iosDevices.DeviceList {
|
|
device, err := NewIOSDevice(
|
|
option.WithUDID(dev.Properties.SerialNumber))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
properties := device.Properties
|
|
err = ios.Pair(dev)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("failed to pair device")
|
|
continue
|
|
}
|
|
serialList = append(serialList, properties.SerialNumber)
|
|
}
|
|
deviceList["iosDevices"] = serialList
|
|
}
|
|
|
|
jsonResult, _ := json.Marshal(deviceList)
|
|
return mcp.NewToolResultText(string(jsonResult)), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolListAvailableDevices) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
return buildMCPCallToolRequest(t.Name(), map[string]any{}), nil
|
|
}
|
|
|
|
// ToolSelectDevice implements the select_device tool call.
|
|
type ToolSelectDevice struct{}
|
|
|
|
func (t *ToolSelectDevice) Name() option.ActionMethod {
|
|
return option.ACTION_SelectDevice
|
|
}
|
|
|
|
func (t *ToolSelectDevice) Description() string {
|
|
return "Select a device to use from the list of available devices. Use the list_available_devices tool to get a list of available devices."
|
|
}
|
|
|
|
func (t *ToolSelectDevice) Options() []mcp.ToolOption {
|
|
return []mcp.ToolOption{
|
|
mcp.WithString("platform", mcp.Enum("android", "ios"), mcp.Description("The type of device to select")),
|
|
mcp.WithString("serial", mcp.Description("The device serial/udid to select")),
|
|
}
|
|
}
|
|
|
|
func (t *ToolSelectDevice) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
uuid := driverExt.IDriver.GetDevice().UUID()
|
|
return mcp.NewToolResultText(fmt.Sprintf("Selected device: %s", uuid)), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolSelectDevice) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
return buildMCPCallToolRequest(t.Name(), map[string]any{}), nil
|
|
}
|
|
|
|
// ToolTapXY implements the tap_xy tool call.
|
|
type ToolTapXY struct{}
|
|
|
|
func (t *ToolTapXY) Name() option.ActionMethod {
|
|
return option.ACTION_TapXY
|
|
}
|
|
|
|
func (t *ToolTapXY) Description() string {
|
|
return "Click on the screen at given x,y coordinates"
|
|
}
|
|
|
|
func (t *ToolTapXY) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_TapXY)
|
|
}
|
|
|
|
func (t *ToolTapXY) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
var unifiedReq option.UnifiedActionRequest
|
|
if err := mapToStruct(request.Params.Arguments, &unifiedReq); err != nil {
|
|
return nil, fmt.Errorf("parse parameters error: %w", err)
|
|
}
|
|
|
|
// Convert to ActionOptions
|
|
actionOpts := unifiedReq.ToActionOptions()
|
|
opts := actionOpts.Options()
|
|
|
|
// Add default options
|
|
opts = append(opts, option.WithPreMarkOperation(true))
|
|
|
|
// Validate required parameters
|
|
if unifiedReq.X == nil || unifiedReq.Y == nil {
|
|
return nil, fmt.Errorf("x and y coordinates are required")
|
|
}
|
|
|
|
// Tap action logic
|
|
log.Info().Float64("x", *unifiedReq.X).Float64("y", *unifiedReq.Y).Msg("tapping at coordinates")
|
|
|
|
err = driverExt.TapXY(*unifiedReq.X, *unifiedReq.Y, opts...)
|
|
if err != nil {
|
|
return mcp.NewToolResultError(fmt.Sprintf("Tap failed: %s", err.Error())), nil
|
|
}
|
|
|
|
return mcp.NewToolResultText(fmt.Sprintf("Successfully tapped at coordinates (%.2f, %.2f)", *unifiedReq.X, *unifiedReq.Y)), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolTapXY) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
if params, err := builtin.ConvertToFloat64Slice(action.Params); err == nil && len(params) == 2 {
|
|
x, y := params[0], params[1]
|
|
arguments := map[string]any{
|
|
"x": x,
|
|
"y": y,
|
|
}
|
|
// Add duration if available from action options
|
|
if duration := action.ActionOptions.Duration; duration > 0 {
|
|
arguments["duration"] = duration
|
|
}
|
|
|
|
// Extract options to arguments
|
|
extractActionOptionsToArguments(action.GetOptions(), arguments)
|
|
|
|
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
|
}
|
|
return mcp.CallToolRequest{}, fmt.Errorf("invalid tap params: %v", action.Params)
|
|
}
|
|
|
|
// ToolTapAbsXY implements the tap_abs_xy tool call.
|
|
type ToolTapAbsXY struct{}
|
|
|
|
func (t *ToolTapAbsXY) Name() option.ActionMethod {
|
|
return option.ACTION_TapAbsXY
|
|
}
|
|
|
|
func (t *ToolTapAbsXY) Description() string {
|
|
return "Tap at absolute pixel coordinates"
|
|
}
|
|
|
|
func (t *ToolTapAbsXY) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_TapAbsXY)
|
|
}
|
|
|
|
func (t *ToolTapAbsXY) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
var tapAbsReq option.TapAbsXYRequest
|
|
if err := mapToStruct(request.Params.Arguments, &tapAbsReq); err != nil {
|
|
return nil, fmt.Errorf("parse parameters error: %w", err)
|
|
}
|
|
|
|
// Build action options from request structure
|
|
var opts []option.ActionOption
|
|
|
|
// Add numeric options
|
|
if tapAbsReq.Duration > 0 {
|
|
opts = append(opts, option.WithDuration(tapAbsReq.Duration))
|
|
}
|
|
|
|
// Tap absolute XY action logic
|
|
log.Info().Float64("x", tapAbsReq.X).Float64("y", tapAbsReq.Y).Msg("tapping at absolute coordinates")
|
|
|
|
err = driverExt.TapAbsXY(tapAbsReq.X, tapAbsReq.Y, opts...)
|
|
if err != nil {
|
|
return mcp.NewToolResultError(fmt.Sprintf("Tap absolute XY failed: %s", err.Error())), nil
|
|
}
|
|
|
|
return mcp.NewToolResultText(fmt.Sprintf("Successfully tapped at absolute coordinates (%.0f, %.0f)", tapAbsReq.X, tapAbsReq.Y)), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolTapAbsXY) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
if params, err := builtin.ConvertToFloat64Slice(action.Params); err == nil && len(params) == 2 {
|
|
x, y := params[0], params[1]
|
|
arguments := map[string]any{
|
|
"x": x,
|
|
"y": y,
|
|
}
|
|
// Add duration if available
|
|
if duration := action.ActionOptions.Duration; duration > 0 {
|
|
arguments["duration"] = duration
|
|
}
|
|
|
|
// Extract options to arguments
|
|
extractActionOptionsToArguments(action.GetOptions(), arguments)
|
|
|
|
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
|
}
|
|
return mcp.CallToolRequest{}, fmt.Errorf("invalid tap abs params: %v", action.Params)
|
|
}
|
|
|
|
// ToolTapByOCR implements the tap_ocr tool call.
|
|
type ToolTapByOCR struct{}
|
|
|
|
func (t *ToolTapByOCR) Name() option.ActionMethod {
|
|
return option.ACTION_TapByOCR
|
|
}
|
|
|
|
func (t *ToolTapByOCR) Description() string {
|
|
return "Tap on text found by OCR recognition"
|
|
}
|
|
|
|
func (t *ToolTapByOCR) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_TapByOCR)
|
|
}
|
|
|
|
func (t *ToolTapByOCR) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
var ocrReq option.TapByOCRRequest
|
|
if err := mapToStruct(request.Params.Arguments, &ocrReq); err != nil {
|
|
return nil, fmt.Errorf("parse parameters error: %w", err)
|
|
}
|
|
|
|
// Build action options from request structure
|
|
var opts []option.ActionOption
|
|
|
|
// Add boolean options
|
|
if ocrReq.IgnoreNotFoundError {
|
|
opts = append(opts, option.WithIgnoreNotFoundError(true))
|
|
}
|
|
if ocrReq.Regex {
|
|
opts = append(opts, option.WithRegex(true))
|
|
}
|
|
if ocrReq.TapRandomRect {
|
|
opts = append(opts, option.WithTapRandomRect(true))
|
|
}
|
|
|
|
// Add numeric options
|
|
if ocrReq.MaxRetryTimes > 0 {
|
|
opts = append(opts, option.WithMaxRetryTimes(ocrReq.MaxRetryTimes))
|
|
}
|
|
if ocrReq.Index > 0 {
|
|
opts = append(opts, option.WithIndex(ocrReq.Index))
|
|
}
|
|
|
|
// Tap by OCR action logic
|
|
log.Info().Str("text", ocrReq.Text).Msg("tapping by OCR")
|
|
err = driverExt.TapByOCR(ocrReq.Text, opts...)
|
|
if err != nil {
|
|
return mcp.NewToolResultError(fmt.Sprintf("Tap by OCR failed: %s", err.Error())), nil
|
|
}
|
|
|
|
return mcp.NewToolResultText(fmt.Sprintf("Successfully tapped on OCR text: %s", ocrReq.Text)), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolTapByOCR) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
if text, ok := action.Params.(string); ok {
|
|
arguments := map[string]any{
|
|
"text": text,
|
|
}
|
|
|
|
// Extract options to arguments
|
|
extractActionOptionsToArguments(action.GetOptions(), arguments)
|
|
|
|
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
|
}
|
|
return mcp.CallToolRequest{}, fmt.Errorf("invalid tap by OCR params: %v", action.Params)
|
|
}
|
|
|
|
// ToolTapByCV implements the tap_cv tool call.
|
|
type ToolTapByCV struct{}
|
|
|
|
func (t *ToolTapByCV) Name() option.ActionMethod {
|
|
return option.ACTION_TapByCV
|
|
}
|
|
|
|
func (t *ToolTapByCV) Description() string {
|
|
return "Tap on element found by computer vision"
|
|
}
|
|
|
|
func (t *ToolTapByCV) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_TapByCV)
|
|
}
|
|
|
|
func (t *ToolTapByCV) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
var cvReq option.TapByCVRequest
|
|
if err := mapToStruct(request.Params.Arguments, &cvReq); err != nil {
|
|
return nil, fmt.Errorf("parse parameters error: %w", err)
|
|
}
|
|
|
|
// Build action options from request structure
|
|
var opts []option.ActionOption
|
|
|
|
// Add boolean options
|
|
if cvReq.IgnoreNotFoundError {
|
|
opts = append(opts, option.WithIgnoreNotFoundError(true))
|
|
}
|
|
if cvReq.TapRandomRect {
|
|
opts = append(opts, option.WithTapRandomRect(true))
|
|
}
|
|
|
|
// Add numeric options
|
|
if cvReq.MaxRetryTimes > 0 {
|
|
opts = append(opts, option.WithMaxRetryTimes(cvReq.MaxRetryTimes))
|
|
}
|
|
if cvReq.Index > 0 {
|
|
opts = append(opts, option.WithIndex(cvReq.Index))
|
|
}
|
|
|
|
// Tap by CV action logic
|
|
log.Info().Str("imagePath", cvReq.ImagePath).Msg("tapping by CV")
|
|
|
|
// For TapByCV, we need to check if there are UI types in the options
|
|
// In the original DoAction, it requires ScreenShotWithUITypes to be set
|
|
// We'll add a basic implementation that triggers CV recognition
|
|
err = driverExt.TapByCV(opts...)
|
|
if err != nil {
|
|
return mcp.NewToolResultError(fmt.Sprintf("Tap by CV failed: %s", err.Error())), nil
|
|
}
|
|
|
|
return mcp.NewToolResultText("Successfully tapped by computer vision"), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolTapByCV) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
// For TapByCV, the original action might not have params but relies on options
|
|
arguments := map[string]any{
|
|
"imagePath": "", // Will be handled by the tool based on UI types
|
|
}
|
|
|
|
// Extract options to arguments
|
|
extractActionOptionsToArguments(action.GetOptions(), arguments)
|
|
|
|
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
|
}
|
|
|
|
// ToolDoubleTapXY implements the double_tap_xy tool call.
|
|
type ToolDoubleTapXY struct{}
|
|
|
|
func (t *ToolDoubleTapXY) Name() option.ActionMethod {
|
|
return option.ACTION_DoubleTapXY
|
|
}
|
|
|
|
func (t *ToolDoubleTapXY) Description() string {
|
|
return "Double tap at given coordinates"
|
|
}
|
|
|
|
func (t *ToolDoubleTapXY) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_DoubleTapXY)
|
|
}
|
|
|
|
func (t *ToolDoubleTapXY) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
var doubleTapReq option.DoubleTapXYRequest
|
|
if err := mapToStruct(request.Params.Arguments, &doubleTapReq); err != nil {
|
|
return nil, fmt.Errorf("parse parameters error: %w", err)
|
|
}
|
|
|
|
// Double tap XY action logic
|
|
log.Info().Float64("x", doubleTapReq.X).Float64("y", doubleTapReq.Y).Msg("double tapping at coordinates")
|
|
err = driverExt.DoubleTap(doubleTapReq.X, doubleTapReq.Y)
|
|
if err != nil {
|
|
return mcp.NewToolResultError(fmt.Sprintf("Double tap failed: %s", err.Error())), nil
|
|
}
|
|
|
|
return mcp.NewToolResultText(fmt.Sprintf("Successfully double tapped at (%.2f, %.2f)", doubleTapReq.X, doubleTapReq.Y)), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolDoubleTapXY) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
if params, err := builtin.ConvertToFloat64Slice(action.Params); err == nil && len(params) == 2 {
|
|
x, y := params[0], params[1]
|
|
arguments := map[string]any{
|
|
"x": x,
|
|
"y": y,
|
|
}
|
|
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
|
}
|
|
return mcp.CallToolRequest{}, fmt.Errorf("invalid double tap params: %v", action.Params)
|
|
}
|
|
|
|
// ToolListPackages implements the list_packages tool call.
|
|
type ToolListPackages struct{}
|
|
|
|
func (t *ToolListPackages) Name() option.ActionMethod {
|
|
return option.ACTION_ListPackages
|
|
}
|
|
|
|
func (t *ToolListPackages) Description() string {
|
|
return "List all the apps/packages on the device."
|
|
}
|
|
|
|
func (t *ToolListPackages) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_ListPackages)
|
|
}
|
|
|
|
func (t *ToolListPackages) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
apps, err := driverExt.IDriver.GetDevice().ListPackages()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return mcp.NewToolResultText(fmt.Sprintf("Device packages: %v", apps)), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolListPackages) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
return buildMCPCallToolRequest(t.Name(), map[string]any{}), nil
|
|
}
|
|
|
|
// ToolLaunchApp implements the launch_app tool call.
|
|
type ToolLaunchApp struct{}
|
|
|
|
func (t *ToolLaunchApp) Name() option.ActionMethod {
|
|
return option.ACTION_AppLaunch
|
|
}
|
|
|
|
func (t *ToolLaunchApp) Description() string {
|
|
return "Launch an app on mobile device. Use this to open a specific app. You can find the package name of the app by calling list_packages."
|
|
}
|
|
|
|
func (t *ToolLaunchApp) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_AppLaunch)
|
|
}
|
|
|
|
func (t *ToolLaunchApp) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
var appLaunchReq option.AppLaunchRequest
|
|
if err := mapToStruct(request.Params.Arguments, &appLaunchReq); err != nil {
|
|
return nil, fmt.Errorf("parse parameters error: %w", err)
|
|
}
|
|
|
|
if appLaunchReq.PackageName == "" {
|
|
return nil, fmt.Errorf("package_name is required")
|
|
}
|
|
|
|
// Launch app action logic
|
|
log.Info().Str("packageName", appLaunchReq.PackageName).Msg("launching app")
|
|
err = driverExt.AppLaunch(appLaunchReq.PackageName)
|
|
if err != nil {
|
|
return mcp.NewToolResultError(fmt.Sprintf("Launch app failed: %s", err.Error())), nil
|
|
}
|
|
|
|
return mcp.NewToolResultText(fmt.Sprintf("Successfully launched app: %s", appLaunchReq.PackageName)), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolLaunchApp) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
if packageName, ok := action.Params.(string); ok {
|
|
arguments := map[string]any{
|
|
"packageName": packageName,
|
|
}
|
|
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
|
}
|
|
return mcp.CallToolRequest{}, fmt.Errorf("invalid app launch params: %v", action.Params)
|
|
}
|
|
|
|
// ToolTerminateApp implements the terminate_app tool call.
|
|
type ToolTerminateApp struct{}
|
|
|
|
func (t *ToolTerminateApp) Name() option.ActionMethod {
|
|
return option.ACTION_AppTerminate
|
|
}
|
|
|
|
func (t *ToolTerminateApp) Description() string {
|
|
return "Stop and terminate an app on mobile device"
|
|
}
|
|
|
|
func (t *ToolTerminateApp) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_AppTerminate)
|
|
}
|
|
|
|
func (t *ToolTerminateApp) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
var appTerminateReq option.AppTerminateRequest
|
|
if err := mapToStruct(request.Params.Arguments, &appTerminateReq); err != nil {
|
|
return nil, fmt.Errorf("parse parameters error: %w", err)
|
|
}
|
|
|
|
if appTerminateReq.PackageName == "" {
|
|
return nil, fmt.Errorf("package_name is required")
|
|
}
|
|
|
|
// Terminate app action logic
|
|
log.Info().Str("packageName", appTerminateReq.PackageName).Msg("terminating app")
|
|
success, err := driverExt.AppTerminate(appTerminateReq.PackageName)
|
|
if err != nil {
|
|
return mcp.NewToolResultError(fmt.Sprintf("Terminate app failed: %s", err.Error())), nil
|
|
}
|
|
if !success {
|
|
log.Warn().Str("packageName", appTerminateReq.PackageName).Msg("app was not running")
|
|
}
|
|
|
|
return mcp.NewToolResultText(fmt.Sprintf("Successfully terminated app: %s", appTerminateReq.PackageName)), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolTerminateApp) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
if packageName, ok := action.Params.(string); ok {
|
|
arguments := map[string]any{
|
|
"packageName": packageName,
|
|
}
|
|
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
|
}
|
|
return mcp.CallToolRequest{}, fmt.Errorf("invalid app terminate params: %v", action.Params)
|
|
}
|
|
|
|
// ToolScreenShot implements the screenshot tool call.
|
|
type ToolScreenShot struct{}
|
|
|
|
func (t *ToolScreenShot) Name() option.ActionMethod {
|
|
return option.ACTION_ScreenShot
|
|
}
|
|
|
|
func (t *ToolScreenShot) Description() string {
|
|
return "Take a screenshot of the mobile device. Use this to understand what's on screen. Do not cache this result."
|
|
}
|
|
|
|
func (t *ToolScreenShot) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_ScreenShot)
|
|
}
|
|
|
|
func (t *ToolScreenShot) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bufferBase64, err := GetScreenShotBufferBase64(driverExt.IDriver)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("ScreenShot failed")
|
|
return mcp.NewToolResultError(fmt.Sprintf("Failed to take screenshot: %v", err)), nil
|
|
}
|
|
log.Debug().Int("imageBytes", len(bufferBase64)).Msg("take screenshot success")
|
|
|
|
return mcp.NewToolResultImage("screenshot", bufferBase64, "image/jpeg"), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolScreenShot) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
return buildMCPCallToolRequest(t.Name(), map[string]any{}), nil
|
|
}
|
|
|
|
// ToolGetScreenSize implements the get_screen_size tool call.
|
|
type ToolGetScreenSize struct{}
|
|
|
|
func (t *ToolGetScreenSize) Name() option.ActionMethod {
|
|
return option.ACTION_GetScreenSize
|
|
}
|
|
|
|
func (t *ToolGetScreenSize) Description() string {
|
|
return "Get the screen size of the mobile device in pixels"
|
|
}
|
|
|
|
func (t *ToolGetScreenSize) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_GetScreenSize)
|
|
}
|
|
|
|
func (t *ToolGetScreenSize) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
screenSize, err := driverExt.IDriver.WindowSize()
|
|
if err != nil {
|
|
return mcp.NewToolResultError("Get screen size failed: " + err.Error()), nil
|
|
}
|
|
return mcp.NewToolResultText(
|
|
fmt.Sprintf("Screen size: %d x %d pixels", screenSize.Width, screenSize.Height),
|
|
), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolGetScreenSize) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
return buildMCPCallToolRequest(t.Name(), map[string]any{}), nil
|
|
}
|
|
|
|
// ToolPressButton implements the press_button tool call.
|
|
type ToolPressButton struct{}
|
|
|
|
func (t *ToolPressButton) Name() option.ActionMethod {
|
|
return option.ACTION_PressButton
|
|
}
|
|
|
|
func (t *ToolPressButton) Description() string {
|
|
return "Press a button on the device"
|
|
}
|
|
|
|
func (t *ToolPressButton) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_PressButton)
|
|
}
|
|
|
|
func (t *ToolPressButton) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
var pressButtonReq option.PressButtonRequest
|
|
if err := mapToStruct(request.Params.Arguments, &pressButtonReq); err != nil {
|
|
return nil, fmt.Errorf("parse parameters error: %w", err)
|
|
}
|
|
|
|
// Press button action logic
|
|
log.Info().Str("button", string(pressButtonReq.Button)).Msg("pressing button")
|
|
err = driverExt.PressButton(types.DeviceButton(pressButtonReq.Button))
|
|
if err != nil {
|
|
return mcp.NewToolResultError(fmt.Sprintf("Press button failed: %s", err.Error())), nil
|
|
}
|
|
|
|
return mcp.NewToolResultText(fmt.Sprintf("Successfully pressed button: %s", pressButtonReq.Button)), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolPressButton) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
if button, ok := action.Params.(string); ok {
|
|
arguments := map[string]any{
|
|
"button": button,
|
|
}
|
|
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
|
}
|
|
return mcp.CallToolRequest{}, fmt.Errorf("invalid press button params: %v", action.Params)
|
|
}
|
|
|
|
// 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{}
|
|
|
|
func (t *ToolSwipe) Name() option.ActionMethod {
|
|
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.UnifiedActionRequest{}
|
|
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.Params.Arguments["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 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 tool call.
|
|
type ToolSwipeDirection struct{}
|
|
|
|
func (t *ToolSwipeDirection) Name() option.ActionMethod {
|
|
return option.ACTION_SwipeDirection
|
|
}
|
|
|
|
func (t *ToolSwipeDirection) Description() string {
|
|
return "Swipe on the screen"
|
|
}
|
|
|
|
func (t *ToolSwipeDirection) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_SwipeDirection)
|
|
}
|
|
|
|
func (t *ToolSwipeDirection) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
var swipeReq option.SwipeRequest
|
|
if err := mapToStruct(request.Params.Arguments, &swipeReq); err != nil {
|
|
return nil, fmt.Errorf("parse parameters error: %w", err)
|
|
}
|
|
|
|
// Swipe action logic
|
|
log.Info().Str("direction", swipeReq.Direction).Msg("performing swipe")
|
|
|
|
// Validate direction
|
|
validDirections := []string{"up", "down", "left", "right"}
|
|
isValid := false
|
|
for _, validDir := range validDirections {
|
|
if swipeReq.Direction == validDir {
|
|
isValid = true
|
|
break
|
|
}
|
|
}
|
|
if !isValid {
|
|
return nil, fmt.Errorf("invalid swipe direction: %s, expected one of: %v", swipeReq.Direction, validDirections)
|
|
}
|
|
|
|
opts := []option.ActionOption{
|
|
option.WithPreMarkOperation(true),
|
|
option.WithDuration(swipeReq.Duration),
|
|
option.WithPressDuration(swipeReq.PressDuration),
|
|
}
|
|
|
|
// Convert direction to coordinates and perform swipe
|
|
switch swipeReq.Direction {
|
|
case "up":
|
|
err = driverExt.Swipe(0.5, 0.5, 0.5, 0.1, opts...)
|
|
case "down":
|
|
err = driverExt.Swipe(0.5, 0.5, 0.5, 0.9, opts...)
|
|
case "left":
|
|
err = driverExt.Swipe(0.5, 0.5, 0.1, 0.5, opts...)
|
|
case "right":
|
|
err = driverExt.Swipe(0.5, 0.5, 0.9, 0.5, opts...)
|
|
default:
|
|
return mcp.NewToolResultError(fmt.Sprintf("Unexpected swipe direction: %s", swipeReq.Direction)), nil
|
|
}
|
|
|
|
if err != nil {
|
|
return mcp.NewToolResultError(fmt.Sprintf("Swipe failed: %s", err.Error())), nil
|
|
}
|
|
|
|
return mcp.NewToolResultText(fmt.Sprintf("Successfully swiped %s", swipeReq.Direction)), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolSwipeDirection) ConvertActionToCallToolRequest(action 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), nil
|
|
}
|
|
return mcp.CallToolRequest{}, fmt.Errorf("invalid swipe params: %v", action.Params)
|
|
}
|
|
|
|
// ToolSwipeCoordinate implements the swipe_advanced tool call.
|
|
type ToolSwipeCoordinate struct{}
|
|
|
|
func (t *ToolSwipeCoordinate) Name() option.ActionMethod {
|
|
return option.ACTION_SwipeCoordinate
|
|
}
|
|
|
|
func (t *ToolSwipeCoordinate) Description() string {
|
|
return "Perform advanced swipe with custom coordinates and timing"
|
|
}
|
|
|
|
func (t *ToolSwipeCoordinate) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_SwipeCoordinate)
|
|
}
|
|
|
|
func (t *ToolSwipeCoordinate) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
var swipeAdvReq option.SwipeAdvancedRequest
|
|
if err := mapToStruct(request.Params.Arguments, &swipeAdvReq); err != nil {
|
|
return nil, fmt.Errorf("parse parameters error: %w", err)
|
|
}
|
|
|
|
// Advanced swipe action logic using prepareSwipeAction like the original DoAction
|
|
log.Info().
|
|
Float64("fromX", swipeAdvReq.FromX).Float64("fromY", swipeAdvReq.FromY).
|
|
Float64("toX", swipeAdvReq.ToX).Float64("toY", swipeAdvReq.ToY).
|
|
Msg("performing advanced swipe")
|
|
|
|
params := []float64{swipeAdvReq.FromX, swipeAdvReq.FromY, swipeAdvReq.ToX, swipeAdvReq.ToY}
|
|
opts := []option.ActionOption{}
|
|
if swipeAdvReq.Duration > 0 {
|
|
opts = append(opts, option.WithDuration(swipeAdvReq.Duration))
|
|
}
|
|
if swipeAdvReq.PressDuration > 0 {
|
|
opts = append(opts, option.WithPressDuration(swipeAdvReq.PressDuration))
|
|
}
|
|
|
|
swipeAction := prepareSwipeAction(driverExt, params, opts...)
|
|
err = swipeAction(driverExt)
|
|
if err != nil {
|
|
return mcp.NewToolResultError(fmt.Sprintf("Advanced swipe failed: %s", err.Error())), nil
|
|
}
|
|
|
|
return mcp.NewToolResultText(fmt.Sprintf("Successfully performed advanced swipe from (%.2f, %.2f) to (%.2f, %.2f)",
|
|
swipeAdvReq.FromX, swipeAdvReq.FromY, swipeAdvReq.ToX, swipeAdvReq.ToY)), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolSwipeCoordinate) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
if paramSlice, err := builtin.ConvertToFloat64Slice(action.Params); err == nil && len(paramSlice) == 4 {
|
|
arguments := map[string]any{
|
|
"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 buildMCPCallToolRequest(t.Name(), arguments), 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{}
|
|
|
|
func (t *ToolSwipeToTapApp) Name() option.ActionMethod {
|
|
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.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_SwipeToTapApp)
|
|
}
|
|
|
|
func (t *ToolSwipeToTapApp) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
var swipeAppReq option.SwipeToTapAppRequest
|
|
if err := mapToStruct(request.Params.Arguments, &swipeAppReq); err != nil {
|
|
return nil, fmt.Errorf("parse parameters error: %w", err)
|
|
}
|
|
|
|
// Build action options from request structure
|
|
var opts []option.ActionOption
|
|
|
|
// Add boolean options
|
|
if swipeAppReq.IgnoreNotFoundError {
|
|
opts = append(opts, option.WithIgnoreNotFoundError(true))
|
|
}
|
|
|
|
// Add numeric options
|
|
if swipeAppReq.MaxRetryTimes > 0 {
|
|
opts = append(opts, option.WithMaxRetryTimes(swipeAppReq.MaxRetryTimes))
|
|
}
|
|
if swipeAppReq.Index > 0 {
|
|
opts = append(opts, option.WithIndex(swipeAppReq.Index))
|
|
}
|
|
|
|
// Swipe to tap app action logic
|
|
log.Info().Str("appName", swipeAppReq.AppName).Msg("swipe to tap app")
|
|
err = driverExt.SwipeToTapApp(swipeAppReq.AppName, opts...)
|
|
if err != nil {
|
|
return mcp.NewToolResultError(fmt.Sprintf("Swipe to tap app failed: %s", err.Error())), nil
|
|
}
|
|
|
|
return mcp.NewToolResultText(fmt.Sprintf("Successfully found and tapped app: %s", swipeAppReq.AppName)), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolSwipeToTapApp) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
if appName, ok := action.Params.(string); ok {
|
|
arguments := map[string]any{
|
|
"appName": appName,
|
|
}
|
|
|
|
// Extract options to arguments
|
|
extractActionOptionsToArguments(action.GetOptions(), arguments)
|
|
|
|
return buildMCPCallToolRequest(t.Name(), arguments), 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{}
|
|
|
|
func (t *ToolSwipeToTapText) Name() option.ActionMethod {
|
|
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.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_SwipeToTapText)
|
|
}
|
|
|
|
func (t *ToolSwipeToTapText) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
var swipeTextReq option.SwipeToTapTextRequest
|
|
if err := mapToStruct(request.Params.Arguments, &swipeTextReq); err != nil {
|
|
return nil, fmt.Errorf("parse parameters error: %w", err)
|
|
}
|
|
|
|
// Build action options from request structure
|
|
var opts []option.ActionOption
|
|
|
|
// Add boolean options
|
|
if swipeTextReq.IgnoreNotFoundError {
|
|
opts = append(opts, option.WithIgnoreNotFoundError(true))
|
|
}
|
|
if swipeTextReq.Regex {
|
|
opts = append(opts, option.WithRegex(true))
|
|
}
|
|
|
|
// Add numeric options
|
|
if swipeTextReq.MaxRetryTimes > 0 {
|
|
opts = append(opts, option.WithMaxRetryTimes(swipeTextReq.MaxRetryTimes))
|
|
}
|
|
if swipeTextReq.Index > 0 {
|
|
opts = append(opts, option.WithIndex(swipeTextReq.Index))
|
|
}
|
|
|
|
// Swipe to tap text action logic
|
|
log.Info().Str("text", swipeTextReq.Text).Msg("swipe to tap text")
|
|
err = driverExt.SwipeToTapTexts([]string{swipeTextReq.Text}, opts...)
|
|
if err != nil {
|
|
return mcp.NewToolResultError(fmt.Sprintf("Swipe to tap text failed: %s", err.Error())), nil
|
|
}
|
|
|
|
return mcp.NewToolResultText(fmt.Sprintf("Successfully found and tapped text: %s", swipeTextReq.Text)), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolSwipeToTapText) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
if text, ok := action.Params.(string); ok {
|
|
arguments := map[string]any{
|
|
"text": text,
|
|
}
|
|
|
|
// Extract options to arguments
|
|
extractActionOptionsToArguments(action.GetOptions(), arguments)
|
|
|
|
return buildMCPCallToolRequest(t.Name(), arguments), 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{}
|
|
|
|
func (t *ToolSwipeToTapTexts) Name() option.ActionMethod {
|
|
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.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_SwipeToTapTexts)
|
|
}
|
|
|
|
func (t *ToolSwipeToTapTexts) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
var swipeTextsReq option.SwipeToTapTextsRequest
|
|
if err := mapToStruct(request.Params.Arguments, &swipeTextsReq); err != nil {
|
|
return nil, fmt.Errorf("parse parameters error: %w", err)
|
|
}
|
|
|
|
// Build action options from request structure
|
|
var opts []option.ActionOption
|
|
|
|
// Add boolean options
|
|
if swipeTextsReq.IgnoreNotFoundError {
|
|
opts = append(opts, option.WithIgnoreNotFoundError(true))
|
|
}
|
|
if swipeTextsReq.Regex {
|
|
opts = append(opts, option.WithRegex(true))
|
|
}
|
|
|
|
// Add numeric options
|
|
if swipeTextsReq.MaxRetryTimes > 0 {
|
|
opts = append(opts, option.WithMaxRetryTimes(swipeTextsReq.MaxRetryTimes))
|
|
}
|
|
if swipeTextsReq.Index > 0 {
|
|
opts = append(opts, option.WithIndex(swipeTextsReq.Index))
|
|
}
|
|
|
|
// Swipe to tap texts action logic
|
|
log.Info().Strs("texts", swipeTextsReq.Texts).Msg("swipe to tap texts")
|
|
err = driverExt.SwipeToTapTexts(swipeTextsReq.Texts, opts...)
|
|
if err != nil {
|
|
return mcp.NewToolResultError(fmt.Sprintf("Swipe to tap texts failed: %s", err.Error())), nil
|
|
}
|
|
|
|
return mcp.NewToolResultText(fmt.Sprintf("Successfully found and tapped one of texts: %v", swipeTextsReq.Texts)), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolSwipeToTapTexts) ConvertActionToCallToolRequest(action 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,
|
|
}
|
|
|
|
// Extract options to arguments
|
|
extractActionOptionsToArguments(action.GetOptions(), arguments)
|
|
|
|
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
|
}
|
|
|
|
// ToolDrag implements the drag tool call.
|
|
type ToolDrag struct{}
|
|
|
|
func (t *ToolDrag) Name() option.ActionMethod {
|
|
return option.ACTION_Drag
|
|
}
|
|
|
|
func (t *ToolDrag) Description() string {
|
|
return "Drag on the mobile device"
|
|
}
|
|
|
|
func (t *ToolDrag) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_Drag)
|
|
}
|
|
|
|
func (t *ToolDrag) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
var dragReq option.DragRequest
|
|
if err := mapToStruct(request.Params.Arguments, &dragReq); err != nil {
|
|
return nil, fmt.Errorf("parse parameters error: %w", err)
|
|
}
|
|
|
|
opts := []option.ActionOption{}
|
|
if dragReq.Duration > 0 {
|
|
opts = append(opts, option.WithDuration(dragReq.Duration/1000.0))
|
|
}
|
|
|
|
// Drag action logic
|
|
log.Info().
|
|
Float64("fromX", dragReq.FromX).Float64("fromY", dragReq.FromY).
|
|
Float64("toX", dragReq.ToX).Float64("toY", dragReq.ToY).
|
|
Msg("performing drag")
|
|
|
|
err = driverExt.Swipe(dragReq.FromX, dragReq.FromY, dragReq.ToX, dragReq.ToY, opts...)
|
|
if err != nil {
|
|
return mcp.NewToolResultError(fmt.Sprintf("Drag failed: %s", err.Error())), nil
|
|
}
|
|
|
|
return mcp.NewToolResultText(fmt.Sprintf("Successfully dragged from (%.2f, %.2f) to (%.2f, %.2f)",
|
|
dragReq.FromX, dragReq.FromY, dragReq.ToX, dragReq.ToY)), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolDrag) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
if paramSlice, err := builtin.ConvertToFloat64Slice(action.Params); err == nil && len(paramSlice) == 4 {
|
|
arguments := map[string]any{
|
|
"fromX": paramSlice[0],
|
|
"fromY": paramSlice[1],
|
|
"toX": paramSlice[2],
|
|
"toY": 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), nil
|
|
}
|
|
return mcp.CallToolRequest{}, fmt.Errorf("invalid drag params: %v", action.Params)
|
|
}
|
|
|
|
// mapToStruct convert map[string]any to target struct
|
|
func mapToStruct(m map[string]any, out interface{}) error {
|
|
b, err := json.Marshal(m)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return json.Unmarshal(b, out)
|
|
}
|
|
|
|
// extractActionOptionsToArguments extracts action options and adds them to arguments map
|
|
// This is a generic helper that can be used by multiple tools
|
|
func extractActionOptionsToArguments(actionOptions []option.ActionOption, arguments map[string]any) {
|
|
if len(actionOptions) == 0 {
|
|
return
|
|
}
|
|
|
|
// Apply all options to a temporary ActionOptions to extract values
|
|
tempOptions := &option.ActionOptions{}
|
|
for _, opt := range actionOptions {
|
|
opt(tempOptions)
|
|
}
|
|
|
|
// Define option mappings for common boolean options
|
|
booleanOptions := map[string]bool{
|
|
"ignore_NotFoundError": tempOptions.IgnoreNotFoundError,
|
|
"regex": tempOptions.Regex,
|
|
"tap_random_rect": tempOptions.TapRandomRect,
|
|
}
|
|
|
|
// Add boolean options only if they are true
|
|
for key, value := range booleanOptions {
|
|
if value {
|
|
arguments[key] = true
|
|
}
|
|
}
|
|
|
|
// Add numeric options only if they have meaningful values
|
|
if tempOptions.MaxRetryTimes > 0 {
|
|
arguments["max_retry_times"] = tempOptions.MaxRetryTimes
|
|
}
|
|
if tempOptions.Index != 0 {
|
|
arguments["index"] = tempOptions.Index
|
|
}
|
|
if tempOptions.Duration > 0 {
|
|
arguments["duration"] = tempOptions.Duration
|
|
}
|
|
if tempOptions.PressDuration > 0 {
|
|
arguments["press_duration"] = tempOptions.PressDuration
|
|
}
|
|
}
|
|
|
|
// ToolHome implements the home tool call.
|
|
type ToolHome struct{}
|
|
|
|
func (t *ToolHome) Name() option.ActionMethod {
|
|
return option.ACTION_Home
|
|
}
|
|
|
|
func (t *ToolHome) Description() string {
|
|
return "Press the home button on the device"
|
|
}
|
|
|
|
func (t *ToolHome) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_Home)
|
|
}
|
|
|
|
func (t *ToolHome) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
// Home action logic
|
|
log.Info().Msg("pressing home button")
|
|
err = driverExt.Home()
|
|
if err != nil {
|
|
return mcp.NewToolResultError(fmt.Sprintf("Home button press failed: %s", err.Error())), nil
|
|
}
|
|
|
|
return mcp.NewToolResultText("Successfully pressed home button"), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolHome) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
return buildMCPCallToolRequest(t.Name(), map[string]any{}), nil
|
|
}
|
|
|
|
// ToolBack implements the back tool call.
|
|
type ToolBack struct{}
|
|
|
|
func (t *ToolBack) Name() option.ActionMethod {
|
|
return option.ACTION_Back
|
|
}
|
|
|
|
func (t *ToolBack) Description() string {
|
|
return "Press the back button on the device"
|
|
}
|
|
|
|
func (t *ToolBack) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_Back)
|
|
}
|
|
|
|
func (t *ToolBack) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
// Back action logic
|
|
log.Info().Msg("pressing back button")
|
|
err = driverExt.Back()
|
|
if err != nil {
|
|
return mcp.NewToolResultError(fmt.Sprintf("Back button press failed: %s", err.Error())), nil
|
|
}
|
|
|
|
return mcp.NewToolResultText("Successfully pressed back button"), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolBack) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
return buildMCPCallToolRequest(t.Name(), map[string]any{}), nil
|
|
}
|
|
|
|
// ToolInput implements the input tool call.
|
|
type ToolInput struct{}
|
|
|
|
func (t *ToolInput) Name() option.ActionMethod {
|
|
return option.ACTION_Input
|
|
}
|
|
|
|
func (t *ToolInput) Description() string {
|
|
return "Input text on the current active element"
|
|
}
|
|
|
|
func (t *ToolInput) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_Input)
|
|
}
|
|
|
|
func (t *ToolInput) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
var inputReq option.InputRequest
|
|
if err := mapToStruct(request.Params.Arguments, &inputReq); err != nil {
|
|
return nil, fmt.Errorf("parse parameters error: %w", err)
|
|
}
|
|
|
|
if inputReq.Text == "" {
|
|
return nil, fmt.Errorf("text is required")
|
|
}
|
|
|
|
// Input action logic
|
|
log.Info().Str("text", inputReq.Text).Msg("inputting text")
|
|
err = driverExt.Input(inputReq.Text)
|
|
if err != nil {
|
|
return mcp.NewToolResultError(fmt.Sprintf("Input failed: %s", err.Error())), nil
|
|
}
|
|
|
|
return mcp.NewToolResultText(fmt.Sprintf("Successfully input text: %s", inputReq.Text)), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolInput) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
text := fmt.Sprintf("%v", action.Params)
|
|
arguments := map[string]any{
|
|
"text": text,
|
|
}
|
|
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
|
}
|
|
|
|
// ToolWebLoginNoneUI implements the web_login_none_ui tool call.
|
|
type ToolWebLoginNoneUI struct{}
|
|
|
|
func (t *ToolWebLoginNoneUI) Name() option.ActionMethod {
|
|
return option.ACTION_WebLoginNoneUI
|
|
}
|
|
|
|
func (t *ToolWebLoginNoneUI) Description() string {
|
|
return "Perform login without UI interaction for web applications"
|
|
}
|
|
|
|
func (t *ToolWebLoginNoneUI) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_WebLoginNoneUI)
|
|
}
|
|
|
|
func (t *ToolWebLoginNoneUI) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
var loginReq option.WebLoginNoneUIRequest
|
|
if err := mapToStruct(request.Params.Arguments, &loginReq); err != nil {
|
|
return nil, fmt.Errorf("parse parameters error: %w", err)
|
|
}
|
|
|
|
// Web login none UI action logic
|
|
log.Info().Str("packageName", loginReq.PackageName).Msg("performing web login without UI")
|
|
driver, ok := driverExt.IDriver.(*BrowserDriver)
|
|
if !ok {
|
|
return nil, fmt.Errorf("invalid browser driver for web login")
|
|
}
|
|
|
|
_, err = driver.LoginNoneUI(loginReq.PackageName, loginReq.PhoneNumber, loginReq.Captcha, loginReq.Password)
|
|
if err != nil {
|
|
return mcp.NewToolResultError(fmt.Sprintf("Web login failed: %s", err.Error())), nil
|
|
}
|
|
|
|
return mcp.NewToolResultText("Successfully performed web login without UI"), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolWebLoginNoneUI) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
return buildMCPCallToolRequest(t.Name(), map[string]any{}), nil
|
|
}
|
|
|
|
// ToolAppInstall implements the app_install tool call.
|
|
type ToolAppInstall struct{}
|
|
|
|
func (t *ToolAppInstall) Name() option.ActionMethod {
|
|
return option.ACTION_AppInstall
|
|
}
|
|
|
|
func (t *ToolAppInstall) Description() string {
|
|
return "Install an app on the device"
|
|
}
|
|
|
|
func (t *ToolAppInstall) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_AppInstall)
|
|
}
|
|
|
|
func (t *ToolAppInstall) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
var installReq option.AppInstallRequest
|
|
if err := mapToStruct(request.Params.Arguments, &installReq); err != nil {
|
|
return nil, fmt.Errorf("parse parameters error: %w", err)
|
|
}
|
|
|
|
// App install action logic
|
|
log.Info().Str("appUrl", installReq.AppUrl).Msg("installing app")
|
|
err = driverExt.GetDevice().Install(installReq.AppUrl)
|
|
if err != nil {
|
|
return mcp.NewToolResultError(fmt.Sprintf("App install failed: %s", err.Error())), nil
|
|
}
|
|
|
|
return mcp.NewToolResultText(fmt.Sprintf("Successfully installed app from: %s", installReq.AppUrl)), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolAppInstall) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
if appUrl, ok := action.Params.(string); ok {
|
|
arguments := map[string]any{
|
|
"appUrl": appUrl,
|
|
}
|
|
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
|
}
|
|
return mcp.CallToolRequest{}, fmt.Errorf("invalid app install params: %v", action.Params)
|
|
}
|
|
|
|
// ToolAppUninstall implements the app_uninstall tool call.
|
|
type ToolAppUninstall struct{}
|
|
|
|
func (t *ToolAppUninstall) Name() option.ActionMethod {
|
|
return option.ACTION_AppUninstall
|
|
}
|
|
|
|
func (t *ToolAppUninstall) Description() string {
|
|
return "Uninstall an app from the device"
|
|
}
|
|
|
|
func (t *ToolAppUninstall) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_AppUninstall)
|
|
}
|
|
|
|
func (t *ToolAppUninstall) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
var uninstallReq option.AppUninstallRequest
|
|
if err := mapToStruct(request.Params.Arguments, &uninstallReq); err != nil {
|
|
return nil, fmt.Errorf("parse parameters error: %w", err)
|
|
}
|
|
|
|
// App uninstall action logic
|
|
log.Info().Str("packageName", uninstallReq.PackageName).Msg("uninstalling app")
|
|
err = driverExt.GetDevice().Uninstall(uninstallReq.PackageName)
|
|
if err != nil {
|
|
return mcp.NewToolResultError(fmt.Sprintf("App uninstall failed: %s", err.Error())), nil
|
|
}
|
|
|
|
return mcp.NewToolResultText(fmt.Sprintf("Successfully uninstalled app: %s", uninstallReq.PackageName)), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolAppUninstall) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
if packageName, ok := action.Params.(string); ok {
|
|
arguments := map[string]any{
|
|
"packageName": packageName,
|
|
}
|
|
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
|
}
|
|
return mcp.CallToolRequest{}, fmt.Errorf("invalid app uninstall params: %v", action.Params)
|
|
}
|
|
|
|
// ToolAppClear implements the app_clear tool call.
|
|
type ToolAppClear struct{}
|
|
|
|
func (t *ToolAppClear) Name() option.ActionMethod {
|
|
return option.ACTION_AppClear
|
|
}
|
|
|
|
func (t *ToolAppClear) Description() string {
|
|
return "Clear app data and cache"
|
|
}
|
|
|
|
func (t *ToolAppClear) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_AppClear)
|
|
}
|
|
|
|
func (t *ToolAppClear) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
var clearReq option.AppClearRequest
|
|
if err := mapToStruct(request.Params.Arguments, &clearReq); err != nil {
|
|
return nil, fmt.Errorf("parse parameters error: %w", err)
|
|
}
|
|
|
|
// App clear action logic
|
|
log.Info().Str("packageName", clearReq.PackageName).Msg("clearing app")
|
|
err = driverExt.AppClear(clearReq.PackageName)
|
|
if err != nil {
|
|
return mcp.NewToolResultError(fmt.Sprintf("App clear failed: %s", err.Error())), nil
|
|
}
|
|
|
|
return mcp.NewToolResultText(fmt.Sprintf("Successfully cleared app: %s", clearReq.PackageName)), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolAppClear) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
if packageName, ok := action.Params.(string); ok {
|
|
arguments := map[string]any{
|
|
"packageName": packageName,
|
|
}
|
|
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
|
}
|
|
return mcp.CallToolRequest{}, fmt.Errorf("invalid app clear params: %v", action.Params)
|
|
}
|
|
|
|
// ToolSecondaryClick implements the secondary_click tool call.
|
|
type ToolSecondaryClick struct{}
|
|
|
|
func (t *ToolSecondaryClick) Name() option.ActionMethod {
|
|
return option.ACTION_SecondaryClick
|
|
}
|
|
|
|
func (t *ToolSecondaryClick) Description() string {
|
|
return "Perform secondary click (right click) at coordinates"
|
|
}
|
|
|
|
func (t *ToolSecondaryClick) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_SecondaryClick)
|
|
}
|
|
|
|
func (t *ToolSecondaryClick) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
var clickReq option.SecondaryClickRequest
|
|
if err := mapToStruct(request.Params.Arguments, &clickReq); err != nil {
|
|
return nil, fmt.Errorf("parse parameters error: %w", err)
|
|
}
|
|
|
|
// Secondary click action logic
|
|
log.Info().Float64("x", clickReq.X).Float64("y", clickReq.Y).Msg("performing secondary click")
|
|
err = driverExt.SecondaryClick(clickReq.X, clickReq.Y)
|
|
if err != nil {
|
|
return mcp.NewToolResultError(fmt.Sprintf("Secondary click failed: %s", err.Error())), nil
|
|
}
|
|
|
|
return mcp.NewToolResultText(fmt.Sprintf("Successfully performed secondary click at (%.2f, %.2f)", clickReq.X, clickReq.Y)), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolSecondaryClick) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
if params, err := builtin.ConvertToFloat64Slice(action.Params); err == nil && len(params) == 2 {
|
|
arguments := map[string]any{
|
|
"x": params[0],
|
|
"y": params[1],
|
|
}
|
|
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
|
}
|
|
return mcp.CallToolRequest{}, fmt.Errorf("invalid secondary click params: %v", action.Params)
|
|
}
|
|
|
|
// ToolHoverBySelector implements the hover_by_selector tool call.
|
|
type ToolHoverBySelector struct{}
|
|
|
|
func (t *ToolHoverBySelector) Name() option.ActionMethod {
|
|
return option.ACTION_HoverBySelector
|
|
}
|
|
|
|
func (t *ToolHoverBySelector) Description() string {
|
|
return "Hover over an element selected by CSS selector or XPath"
|
|
}
|
|
|
|
func (t *ToolHoverBySelector) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_HoverBySelector)
|
|
}
|
|
|
|
func (t *ToolHoverBySelector) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
var selectorReq option.SelectorRequest
|
|
if err := mapToStruct(request.Params.Arguments, &selectorReq); err != nil {
|
|
return nil, fmt.Errorf("parse parameters error: %w", err)
|
|
}
|
|
|
|
// Hover by selector action logic
|
|
log.Info().Str("selector", selectorReq.Selector).Msg("hovering by selector")
|
|
err = driverExt.HoverBySelector(selectorReq.Selector)
|
|
if err != nil {
|
|
return mcp.NewToolResultError(fmt.Sprintf("Hover by selector failed: %s", err.Error())), nil
|
|
}
|
|
|
|
return mcp.NewToolResultText(fmt.Sprintf("Successfully hovered over element with selector: %s", selectorReq.Selector)), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolHoverBySelector) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
if selector, ok := action.Params.(string); ok {
|
|
arguments := map[string]any{
|
|
"selector": selector,
|
|
}
|
|
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
|
}
|
|
return mcp.CallToolRequest{}, fmt.Errorf("invalid hover by selector params: %v", action.Params)
|
|
}
|
|
|
|
// ToolTapBySelector implements the tap_by_selector tool call.
|
|
type ToolTapBySelector struct{}
|
|
|
|
func (t *ToolTapBySelector) Name() option.ActionMethod {
|
|
return option.ACTION_TapBySelector
|
|
}
|
|
|
|
func (t *ToolTapBySelector) Description() string {
|
|
return "Tap an element selected by CSS selector or XPath"
|
|
}
|
|
|
|
func (t *ToolTapBySelector) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_TapBySelector)
|
|
}
|
|
|
|
func (t *ToolTapBySelector) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
var selectorReq option.SelectorRequest
|
|
if err := mapToStruct(request.Params.Arguments, &selectorReq); err != nil {
|
|
return nil, fmt.Errorf("parse parameters error: %w", err)
|
|
}
|
|
|
|
// Tap by selector action logic
|
|
log.Info().Str("selector", selectorReq.Selector).Msg("tapping by selector")
|
|
err = driverExt.TapBySelector(selectorReq.Selector)
|
|
if err != nil {
|
|
return mcp.NewToolResultError(fmt.Sprintf("Tap by selector failed: %s", err.Error())), nil
|
|
}
|
|
|
|
return mcp.NewToolResultText(fmt.Sprintf("Successfully tapped element with selector: %s", selectorReq.Selector)), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolTapBySelector) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
if selector, ok := action.Params.(string); ok {
|
|
arguments := map[string]any{
|
|
"selector": selector,
|
|
}
|
|
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
|
}
|
|
return mcp.CallToolRequest{}, fmt.Errorf("invalid tap by selector params: %v", action.Params)
|
|
}
|
|
|
|
// ToolSecondaryClickBySelector implements the secondary_click_by_selector tool call.
|
|
type ToolSecondaryClickBySelector struct{}
|
|
|
|
func (t *ToolSecondaryClickBySelector) Name() option.ActionMethod {
|
|
return option.ACTION_SecondaryClickBySelector
|
|
}
|
|
|
|
func (t *ToolSecondaryClickBySelector) Description() string {
|
|
return "Perform secondary click on an element selected by CSS selector or XPath"
|
|
}
|
|
|
|
func (t *ToolSecondaryClickBySelector) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_SecondaryClickBySelector)
|
|
}
|
|
|
|
func (t *ToolSecondaryClickBySelector) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
var selectorReq option.SelectorRequest
|
|
if err := mapToStruct(request.Params.Arguments, &selectorReq); err != nil {
|
|
return nil, fmt.Errorf("parse parameters error: %w", err)
|
|
}
|
|
|
|
// Secondary click by selector action logic
|
|
log.Info().Str("selector", selectorReq.Selector).Msg("performing secondary click by selector")
|
|
err = driverExt.SecondaryClickBySelector(selectorReq.Selector)
|
|
if err != nil {
|
|
return mcp.NewToolResultError(fmt.Sprintf("Secondary click by selector failed: %s", err.Error())), nil
|
|
}
|
|
|
|
return mcp.NewToolResultText(fmt.Sprintf("Successfully performed secondary click on element with selector: %s", selectorReq.Selector)), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolSecondaryClickBySelector) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
if selector, ok := action.Params.(string); ok {
|
|
arguments := map[string]any{
|
|
"selector": selector,
|
|
}
|
|
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
|
}
|
|
return mcp.CallToolRequest{}, fmt.Errorf("invalid secondary click by selector params: %v", action.Params)
|
|
}
|
|
|
|
// ToolWebCloseTab implements the web_close_tab tool call.
|
|
type ToolWebCloseTab struct{}
|
|
|
|
func (t *ToolWebCloseTab) Name() option.ActionMethod {
|
|
return option.ACTION_WebCloseTab
|
|
}
|
|
|
|
func (t *ToolWebCloseTab) Description() string {
|
|
return "Close a browser tab by index"
|
|
}
|
|
|
|
func (t *ToolWebCloseTab) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_WebCloseTab)
|
|
}
|
|
|
|
func (t *ToolWebCloseTab) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
var closeTabReq option.WebCloseTabRequest
|
|
if err := mapToStruct(request.Params.Arguments, &closeTabReq); err != nil {
|
|
return nil, fmt.Errorf("parse parameters error: %w", err)
|
|
}
|
|
|
|
// Web close tab action logic
|
|
log.Info().Int("tabIndex", closeTabReq.TabIndex).Msg("closing web tab")
|
|
browserDriver, ok := driverExt.IDriver.(*BrowserDriver)
|
|
if !ok {
|
|
return nil, fmt.Errorf("web close tab is only supported for browser drivers")
|
|
}
|
|
|
|
err = browserDriver.CloseTab(closeTabReq.TabIndex)
|
|
if err != nil {
|
|
return mcp.NewToolResultError(fmt.Sprintf("Close tab failed: %s", err.Error())), nil
|
|
}
|
|
|
|
return mcp.NewToolResultText(fmt.Sprintf("Successfully closed tab at index: %d", closeTabReq.TabIndex)), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolWebCloseTab) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
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]any{
|
|
"tabIndex": tabIndex,
|
|
}
|
|
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
|
}
|
|
|
|
// ToolSetIme implements the set_ime tool call.
|
|
type ToolSetIme struct{}
|
|
|
|
func (t *ToolSetIme) Name() option.ActionMethod {
|
|
return option.ACTION_SetIme
|
|
}
|
|
|
|
func (t *ToolSetIme) Description() string {
|
|
return "Set the input method editor (IME) on the device"
|
|
}
|
|
|
|
func (t *ToolSetIme) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_SetIme)
|
|
}
|
|
|
|
func (t *ToolSetIme) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
var imeReq option.SetImeRequest
|
|
if err := mapToStruct(request.Params.Arguments, &imeReq); err != nil {
|
|
return nil, fmt.Errorf("parse parameters error: %w", err)
|
|
}
|
|
|
|
// Set IME action logic
|
|
log.Info().Str("ime", imeReq.Ime).Msg("setting IME")
|
|
err = driverExt.SetIme(imeReq.Ime)
|
|
if err != nil {
|
|
return mcp.NewToolResultError(fmt.Sprintf("Set IME failed: %s", err.Error())), nil
|
|
}
|
|
|
|
return mcp.NewToolResultText(fmt.Sprintf("Successfully set IME to: %s", imeReq.Ime)), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolSetIme) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
if ime, ok := action.Params.(string); ok {
|
|
arguments := map[string]any{
|
|
"ime": ime,
|
|
}
|
|
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
|
}
|
|
return mcp.CallToolRequest{}, fmt.Errorf("invalid set ime params: %v", action.Params)
|
|
}
|
|
|
|
// ToolGetSource implements the get_source tool call.
|
|
type ToolGetSource struct{}
|
|
|
|
func (t *ToolGetSource) Name() option.ActionMethod {
|
|
return option.ACTION_GetSource
|
|
}
|
|
|
|
func (t *ToolGetSource) Description() string {
|
|
return "Get the source/hierarchy of the current screen"
|
|
}
|
|
|
|
func (t *ToolGetSource) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_GetSource)
|
|
}
|
|
|
|
func (t *ToolGetSource) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
var sourceReq option.GetSourceRequest
|
|
if err := mapToStruct(request.Params.Arguments, &sourceReq); err != nil {
|
|
return nil, fmt.Errorf("parse parameters error: %w", err)
|
|
}
|
|
|
|
// Get source action logic
|
|
log.Info().Str("packageName", sourceReq.PackageName).Msg("getting source")
|
|
_, err = driverExt.Source(option.WithProcessName(sourceReq.PackageName))
|
|
if err != nil {
|
|
return mcp.NewToolResultError(fmt.Sprintf("Get source failed: %s", err.Error())), nil
|
|
}
|
|
|
|
return mcp.NewToolResultText(fmt.Sprintf("Successfully retrieved source for package: %s", sourceReq.PackageName)), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolGetSource) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
if packageName, ok := action.Params.(string); ok {
|
|
arguments := map[string]any{
|
|
"packageName": packageName,
|
|
}
|
|
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
|
}
|
|
return mcp.CallToolRequest{}, fmt.Errorf("invalid get source params: %v", action.Params)
|
|
}
|
|
|
|
// ToolSleep implements the sleep tool call.
|
|
type ToolSleep struct{}
|
|
|
|
func (t *ToolSleep) Name() option.ActionMethod {
|
|
return option.ACTION_Sleep
|
|
}
|
|
|
|
func (t *ToolSleep) Description() string {
|
|
return "Sleep for a specified number of seconds"
|
|
}
|
|
|
|
func (t *ToolSleep) Options() []mcp.ToolOption {
|
|
return []mcp.ToolOption{
|
|
mcp.WithNumber("seconds", mcp.Description("Number of seconds to sleep")),
|
|
}
|
|
}
|
|
|
|
func (t *ToolSleep) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
seconds, ok := request.Params.Arguments["seconds"]
|
|
if !ok {
|
|
return nil, fmt.Errorf("seconds parameter is required")
|
|
}
|
|
|
|
// Sleep action logic
|
|
log.Info().Interface("seconds", seconds).Msg("sleeping")
|
|
|
|
var duration time.Duration
|
|
switch v := seconds.(type) {
|
|
case float64:
|
|
duration = time.Duration(v*1000) * time.Millisecond
|
|
case int:
|
|
duration = time.Duration(v) * time.Second
|
|
case int64:
|
|
duration = time.Duration(v) * time.Second
|
|
case string:
|
|
s, err := builtin.ConvertToFloat64(v)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid sleep duration: %v", v)
|
|
}
|
|
duration = time.Duration(s*1000) * time.Millisecond
|
|
default:
|
|
return nil, fmt.Errorf("unsupported sleep duration type: %T", v)
|
|
}
|
|
|
|
time.Sleep(duration)
|
|
|
|
return mcp.NewToolResultText(fmt.Sprintf("Successfully slept for %v seconds", seconds)), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolSleep) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
arguments := map[string]any{
|
|
"seconds": action.Params,
|
|
}
|
|
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
|
}
|
|
|
|
// ToolSleepMS implements the sleep_ms tool call.
|
|
type ToolSleepMS struct{}
|
|
|
|
func (t *ToolSleepMS) Name() option.ActionMethod {
|
|
return option.ACTION_SleepMS
|
|
}
|
|
|
|
func (t *ToolSleepMS) Description() string {
|
|
return "Sleep for specified milliseconds"
|
|
}
|
|
|
|
func (t *ToolSleepMS) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_SleepMS)
|
|
}
|
|
|
|
func (t *ToolSleepMS) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
var sleepReq option.SleepMSRequest
|
|
if err := mapToStruct(request.Params.Arguments, &sleepReq); err != nil {
|
|
return nil, fmt.Errorf("parse parameters error: %w", err)
|
|
}
|
|
|
|
// Sleep MS action logic
|
|
log.Info().Int64("milliseconds", sleepReq.Milliseconds).Msg("sleeping in milliseconds")
|
|
time.Sleep(time.Duration(sleepReq.Milliseconds) * time.Millisecond)
|
|
|
|
return mcp.NewToolResultText(fmt.Sprintf("Successfully slept for %d milliseconds", sleepReq.Milliseconds)), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolSleepMS) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
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]any{
|
|
"milliseconds": milliseconds,
|
|
}
|
|
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
|
}
|
|
|
|
// ToolSleepRandom implements the sleep_random tool call.
|
|
type ToolSleepRandom struct{}
|
|
|
|
func (t *ToolSleepRandom) Name() option.ActionMethod {
|
|
return option.ACTION_SleepRandom
|
|
}
|
|
|
|
func (t *ToolSleepRandom) Description() string {
|
|
return "Sleep for a random duration based on parameters"
|
|
}
|
|
|
|
func (t *ToolSleepRandom) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_SleepRandom)
|
|
}
|
|
|
|
func (t *ToolSleepRandom) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
var sleepRandomReq option.SleepRandomRequest
|
|
if err := mapToStruct(request.Params.Arguments, &sleepRandomReq); err != nil {
|
|
return nil, fmt.Errorf("parse parameters error: %w", err)
|
|
}
|
|
|
|
// Sleep random action logic
|
|
log.Info().Floats64("params", sleepRandomReq.Params).Msg("sleeping for random duration")
|
|
sleepStrict(time.Now(), getSimulationDuration(sleepRandomReq.Params))
|
|
|
|
return mcp.NewToolResultText(fmt.Sprintf("Successfully slept for random duration with params: %v", sleepRandomReq.Params)), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolSleepRandom) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
if params, err := builtin.ConvertToFloat64Slice(action.Params); err == nil {
|
|
arguments := map[string]any{
|
|
"params": params,
|
|
}
|
|
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
|
}
|
|
return mcp.CallToolRequest{}, fmt.Errorf("invalid sleep random params: %v", action.Params)
|
|
}
|
|
|
|
// ToolClosePopups implements the close_popups tool call.
|
|
type ToolClosePopups struct{}
|
|
|
|
func (t *ToolClosePopups) Name() option.ActionMethod {
|
|
return option.ACTION_ClosePopups
|
|
}
|
|
|
|
func (t *ToolClosePopups) Description() string {
|
|
return "Close any popup windows or dialogs on screen"
|
|
}
|
|
|
|
func (t *ToolClosePopups) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_ClosePopups)
|
|
}
|
|
|
|
func (t *ToolClosePopups) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
// Close popups action logic
|
|
log.Info().Msg("closing popups")
|
|
err = driverExt.ClosePopupsHandler()
|
|
if err != nil {
|
|
return mcp.NewToolResultError(fmt.Sprintf("Close popups failed: %s", err.Error())), nil
|
|
}
|
|
|
|
return mcp.NewToolResultText("Successfully closed popups"), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolClosePopups) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
return buildMCPCallToolRequest(t.Name(), map[string]any{}), nil
|
|
}
|
|
|
|
// ToolAIAction implements the ai_action tool call.
|
|
type ToolAIAction struct{}
|
|
|
|
func (t *ToolAIAction) Name() option.ActionMethod {
|
|
return option.ACTION_AIAction
|
|
}
|
|
|
|
func (t *ToolAIAction) Description() string {
|
|
return "Perform actions using AI with a given prompt"
|
|
}
|
|
|
|
func (t *ToolAIAction) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_AIAction)
|
|
}
|
|
|
|
func (t *ToolAIAction) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
driverExt, err := setupXTDriver(ctx, request.Params.Arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("setup driver failed: %w", err)
|
|
}
|
|
|
|
var aiReq option.AIActionRequest
|
|
if err := mapToStruct(request.Params.Arguments, &aiReq); err != nil {
|
|
return nil, fmt.Errorf("parse parameters error: %w", err)
|
|
}
|
|
|
|
// AI action logic
|
|
log.Info().Str("prompt", aiReq.Prompt).Msg("performing AI action")
|
|
err = driverExt.AIAction(aiReq.Prompt)
|
|
if err != nil {
|
|
return mcp.NewToolResultError(fmt.Sprintf("AI action failed: %s", err.Error())), nil
|
|
}
|
|
|
|
return mcp.NewToolResultText(fmt.Sprintf("Successfully performed AI action with prompt: %s", aiReq.Prompt)), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolAIAction) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
if prompt, ok := action.Params.(string); ok {
|
|
arguments := map[string]any{
|
|
"prompt": prompt,
|
|
}
|
|
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
|
}
|
|
return mcp.CallToolRequest{}, fmt.Errorf("invalid AI action params: %v", action.Params)
|
|
}
|
|
|
|
// ToolFinished implements the finished tool call.
|
|
type ToolFinished struct{}
|
|
|
|
func (t *ToolFinished) Name() option.ActionMethod {
|
|
return option.ACTION_Finished
|
|
}
|
|
|
|
func (t *ToolFinished) Description() string {
|
|
return "Mark task as completed with a result message"
|
|
}
|
|
|
|
func (t *ToolFinished) Options() []mcp.ToolOption {
|
|
unifiedReq := &option.UnifiedActionRequest{}
|
|
return unifiedReq.GetMCPOptions(option.ACTION_Finished)
|
|
}
|
|
|
|
func (t *ToolFinished) Implement() server.ToolHandlerFunc {
|
|
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
var finishedReq option.FinishedRequest
|
|
if err := mapToStruct(request.Params.Arguments, &finishedReq); err != nil {
|
|
return nil, fmt.Errorf("parse parameters error: %w", err)
|
|
}
|
|
log.Info().Str("reason", finishedReq.Content).Msg("task finished")
|
|
|
|
return mcp.NewToolResultText(fmt.Sprintf("Task completed: %s", finishedReq.Content)), nil
|
|
}
|
|
}
|
|
|
|
func (t *ToolFinished) ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) {
|
|
if reason, ok := action.Params.(string); ok {
|
|
arguments := map[string]any{
|
|
"content": reason,
|
|
}
|
|
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
|
}
|
|
return mcp.CallToolRequest{}, fmt.Errorf("invalid finished params: %v", action.Params)
|
|
}
|