refactor: replace copy with set alias

This commit is contained in:
lilong.129
2025-07-21 18:01:37 +08:00
parent 739fb42d27
commit 9e59b7ff9b
7 changed files with 18 additions and 405 deletions

View File

@@ -61,9 +61,25 @@ func (s *MCPServer4XTDriver) GetToolByAction(actionMethod option.ActionName) Act
if s.actionToolMap == nil {
return nil
}
actionMethod = getActionNameByAlias(actionMethod)
return s.actionToolMap[actionMethod]
}
func getActionNameByAlias(actionMethod option.ActionName) option.ActionName {
switch strings.ToLower(string(actionMethod)) {
case "terminal_app":
return option.ACTION_AppTerminate
case "open_app":
return option.ACTION_AppLaunch
case "text":
return option.ACTION_Input
case "tap":
return option.ACTION_TapXY
default:
return actionMethod
}
}
// registerTools registers all MCP tools.
func (s *MCPServer4XTDriver) registerTools() {
// Device Tool
@@ -71,7 +87,6 @@ func (s *MCPServer4XTDriver) registerTools() {
s.registerTool(&ToolSelectDevice{}) // SelectDevice
// Touch Tools
s.registerTool(&ToolTap{}) // tap
s.registerTool(&ToolTapXY{}) // tap xy
s.registerTool(&ToolTapAbsXY{}) // tap abs xy
s.registerTool(&ToolTapByOCR{}) // tap by OCR
@@ -89,7 +104,6 @@ func (s *MCPServer4XTDriver) registerTools() {
// Input Tools
s.registerTool(&ToolInput{})
s.registerTool(&ToolText{})
s.registerTool(&ToolBackspace{})
s.registerTool(&ToolSetIme{})
@@ -101,9 +115,7 @@ func (s *MCPServer4XTDriver) registerTools() {
// App Tools
s.registerTool(&ToolListPackages{}) // ListPackages
s.registerTool(&ToolLaunchApp{}) // LaunchApp
s.registerTool(&ToolOpenApp{}) // OpenApp
s.registerTool(&ToolTerminateApp{}) // TerminateApp
s.registerTool(&ToolTerminateAppNew{}) // TerminateApp (new)
s.registerTool(&ToolColdLaunch{}) // ColdLaunch
s.registerTool(&ToolAppInstall{}) // AppInstall
s.registerTool(&ToolAppUninstall{}) // AppUninstall

View File

@@ -79,7 +79,6 @@ func TestToolInterfaces(t *testing.T) {
tools := []ActionTool{
&ToolListAvailableDevices{},
&ToolSelectDevice{},
&ToolTap{},
&ToolTapXY{},
&ToolTapAbsXY{},
&ToolTapByOCR{},
@@ -93,7 +92,6 @@ func TestToolInterfaces(t *testing.T) {
&ToolSwipeToTapTexts{},
&ToolDrag{},
&ToolInput{},
&ToolText{},
&ToolBackspace{},
&ToolScreenShot{},
&ToolGetScreenSize{},
@@ -102,9 +100,7 @@ func TestToolInterfaces(t *testing.T) {
&ToolBack{},
&ToolListPackages{},
&ToolLaunchApp{},
&ToolOpenApp{},
&ToolTerminateApp{},
&ToolTerminateAppNew{},
&ToolColdLaunch{},
&ToolAppInstall{},
&ToolAppUninstall{},
@@ -246,45 +242,6 @@ func TestToolSelectDevice(t *testing.T) {
assert.Equal(t, string(option.ACTION_SelectDevice), request.Params.Name)
}
// TestToolTap tests the ToolTap implementation
func TestToolTap(t *testing.T) {
tool := &ToolTap{}
// Test Name
assert.Equal(t, option.ACTION_Tap, tool.Name())
// Test Description
assert.NotEmpty(t, tool.Description())
// Test Options
options := tool.Options()
assert.NotNil(t, options)
// Test ConvertActionToCallToolRequest with valid params
action := option.MobileAction{
Method: option.ACTION_Tap,
Params: []float64{0.5, 0.6},
ActionOptions: option.ActionOptions{
Duration: 1.5,
},
}
request, err := tool.ConvertActionToCallToolRequest(action)
assert.NoError(t, err)
assert.Equal(t, string(option.ACTION_Tap), request.Params.Name)
args := request.GetArguments()
assert.Equal(t, 0.5, args["x"])
assert.Equal(t, 0.6, args["y"])
assert.Equal(t, 1.5, args["duration"])
// Test ConvertActionToCallToolRequest with invalid params
invalidAction := option.MobileAction{
Method: option.ACTION_Tap,
Params: "invalid",
}
_, err = tool.ConvertActionToCallToolRequest(invalidAction)
assert.Error(t, err)
}
// TestToolTapXY tests the ToolTapXY implementation
func TestToolTapXY(t *testing.T) {
tool := &ToolTapXY{}
@@ -827,31 +784,6 @@ func TestToolInput(t *testing.T) {
assert.Equal(t, "Hello World", request.GetArguments()["text"])
}
// TestToolText tests the ToolText implementation
func TestToolText(t *testing.T) {
tool := &ToolText{}
// Test Name
assert.Equal(t, option.ACTION_Text, tool.Name())
// Test Description
assert.NotEmpty(t, tool.Description())
// Test Options
options := tool.Options()
assert.NotNil(t, options)
// Test ConvertActionToCallToolRequest with valid params
action := option.MobileAction{
Method: option.ACTION_Text,
Params: "Hello World",
}
request, err := tool.ConvertActionToCallToolRequest(action)
assert.NoError(t, err)
assert.Equal(t, string(option.ACTION_Text), request.Params.Name)
assert.Equal(t, "Hello World", request.GetArguments()["text"])
}
// TestToolBackspace tests the ToolBackspace implementation
func TestToolBackspace(t *testing.T) {
tool := &ToolBackspace{}
@@ -1086,39 +1018,6 @@ func TestToolLaunchApp(t *testing.T) {
assert.Error(t, err)
}
// TestToolOpenApp tests the ToolOpenApp implementation
func TestToolOpenApp(t *testing.T) {
tool := &ToolOpenApp{}
// Test Name
assert.Equal(t, option.ACTION_OpenApp, tool.Name())
// Test Description
assert.NotEmpty(t, tool.Description())
// Test Options
options := tool.Options()
assert.NotNil(t, options)
// Test ConvertActionToCallToolRequest with valid params
action := option.MobileAction{
Method: option.ACTION_OpenApp,
Params: "com.example.app",
}
request, err := tool.ConvertActionToCallToolRequest(action)
assert.NoError(t, err)
assert.Equal(t, string(option.ACTION_OpenApp), request.Params.Name)
assert.Equal(t, "com.example.app", request.GetArguments()["packageName"])
// Test ConvertActionToCallToolRequest with invalid params
invalidAction := option.MobileAction{
Method: option.ACTION_OpenApp,
Params: 123, // should be string
}
_, err = tool.ConvertActionToCallToolRequest(invalidAction)
assert.Error(t, err)
}
// TestToolTerminateApp tests the ToolTerminateApp implementation
func TestToolTerminateApp(t *testing.T) {
tool := &ToolTerminateApp{}
@@ -1152,39 +1051,6 @@ func TestToolTerminateApp(t *testing.T) {
assert.Error(t, err)
}
// TestToolTerminateAppNew tests the ToolTerminateAppNew implementation
func TestToolTerminateAppNew(t *testing.T) {
tool := &ToolTerminateAppNew{}
// Test Name
assert.Equal(t, option.ACTION_TerminateApp, tool.Name())
// Test Description
assert.NotEmpty(t, tool.Description())
// Test Options
options := tool.Options()
assert.NotNil(t, options)
// Test ConvertActionToCallToolRequest with valid params
action := option.MobileAction{
Method: option.ACTION_TerminateApp,
Params: "com.example.app",
}
request, err := tool.ConvertActionToCallToolRequest(action)
assert.NoError(t, err)
assert.Equal(t, string(option.ACTION_TerminateApp), request.Params.Name)
assert.Equal(t, "com.example.app", request.GetArguments()["packageName"])
// Test ConvertActionToCallToolRequest with invalid params
invalidAction := option.MobileAction{
Method: option.ACTION_TerminateApp,
Params: []int{1, 2, 3}, // should be string
}
_, err = tool.ConvertActionToCallToolRequest(invalidAction)
assert.Error(t, err)
}
// TestToolColdLaunch tests the ToolColdLaunch implementation
func TestToolColdLaunch(t *testing.T) {
tool := &ToolColdLaunch{}

View File

@@ -395,131 +395,6 @@ func (t *ToolGetForegroundApp) ConvertActionToCallToolRequest(action option.Mobi
return BuildMCPCallToolRequest(t.Name(), map[string]any{}, action), nil
}
// ToolOpenApp implements the open_app tool call.
type ToolOpenApp struct {
// Return data fields - these define the structure of data returned by this tool
PackageName string `json:"packageName" desc:"Package name of the opened app"`
}
func (t *ToolOpenApp) Name() option.ActionName {
return option.ACTION_OpenApp
}
func (t *ToolOpenApp) Description() string {
return "Open an app on mobile device using its package name and wait for the app to load"
}
func (t *ToolOpenApp) Options() []mcp.ToolOption {
unifiedReq := &option.ActionOptions{}
return unifiedReq.GetMCPOptions(option.ACTION_OpenApp)
}
func (t *ToolOpenApp) 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
}
if unifiedReq.PackageName == "" {
return nil, fmt.Errorf("package_name is required")
}
// Open app action logic
err = driverExt.AppLaunch(unifiedReq.PackageName)
if err != nil {
return NewMCPErrorResponse(fmt.Sprintf("Open app failed: %s", err.Error())), err
}
message := fmt.Sprintf("Successfully opened app: %s", unifiedReq.PackageName)
returnData := ToolOpenApp{PackageName: unifiedReq.PackageName}
return NewMCPSuccessResponse(message, &returnData), nil
}
}
func (t *ToolOpenApp) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) {
if packageName, ok := action.Params.(string); ok {
arguments := map[string]any{
"packageName": packageName,
}
return BuildMCPCallToolRequest(t.Name(), arguments, action), nil
}
return mcp.CallToolRequest{}, fmt.Errorf("invalid open app params: %v", action.Params)
}
// ToolTerminateAppNew implements the terminal_app tool call.
type ToolTerminateAppNew struct {
// Return data fields - these define the structure of data returned by this tool
PackageName string `json:"packageName" desc:"Package name of the terminated app"`
WasRunning bool `json:"wasRunning" desc:"Whether the app was actually running before termination"`
}
func (t *ToolTerminateAppNew) Name() option.ActionName {
return option.ACTION_TerminateApp
}
func (t *ToolTerminateAppNew) Description() string {
return "Terminate a running app on mobile device using its package name"
}
func (t *ToolTerminateAppNew) Options() []mcp.ToolOption {
unifiedReq := &option.ActionOptions{}
return unifiedReq.GetMCPOptions(option.ACTION_TerminateApp)
}
func (t *ToolTerminateAppNew) 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
}
if unifiedReq.PackageName == "" {
return nil, fmt.Errorf("package_name is required")
}
// Terminate app action logic
success, err := driverExt.AppTerminate(unifiedReq.PackageName)
if err != nil {
return NewMCPErrorResponse(fmt.Sprintf("Terminate app failed: %s", err.Error())), err
}
if !success {
log.Warn().Str("packageName", unifiedReq.PackageName).Msg("app was not running")
}
message := fmt.Sprintf("Successfully terminated app: %s", unifiedReq.PackageName)
returnData := ToolTerminateAppNew{
PackageName: unifiedReq.PackageName,
WasRunning: success,
}
return NewMCPSuccessResponse(message, &returnData), nil
}
}
func (t *ToolTerminateAppNew) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) {
if packageName, ok := action.Params.(string); ok {
arguments := map[string]any{
"packageName": packageName,
}
return BuildMCPCallToolRequest(t.Name(), arguments, action), nil
}
return mcp.CallToolRequest{}, fmt.Errorf("invalid terminate app params: %v", action.Params)
}
// ToolColdLaunch implements the cold_launch tool call.
type ToolColdLaunch struct {
// Return data fields - these define the structure of data returned by this tool

View File

@@ -124,65 +124,6 @@ func (t *ToolSetIme) ConvertActionToCallToolRequest(action option.MobileAction)
return mcp.CallToolRequest{}, fmt.Errorf("invalid set ime params: %v", action.Params)
}
// ToolText implements the text tool call.
type ToolText struct {
// Return data fields - these define the structure of data returned by this tool
Text string `json:"text" desc:"Text that was input"`
}
func (t *ToolText) Name() option.ActionName {
return option.ACTION_Text
}
func (t *ToolText) Description() string {
return "Input text into the currently focused element or input field"
}
func (t *ToolText) Options() []mcp.ToolOption {
unifiedReq := &option.ActionOptions{}
return unifiedReq.GetMCPOptions(option.ACTION_Text)
}
func (t *ToolText) 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
}
if unifiedReq.Text == "" {
return nil, fmt.Errorf("text is required")
}
opts := unifiedReq.Options()
// Text input action logic
err = driverExt.Input(unifiedReq.Text, opts...)
if err != nil {
return NewMCPErrorResponse(fmt.Sprintf("Text input failed: %s", err.Error())), err
}
message := fmt.Sprintf("Successfully input text: %s", unifiedReq.Text)
returnData := ToolText{Text: unifiedReq.Text}
return NewMCPSuccessResponse(message, &returnData), nil
}
}
func (t *ToolText) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) {
text := fmt.Sprintf("%v", action.Params)
arguments := map[string]any{
"text": text,
}
return BuildMCPCallToolRequest(t.Name(), arguments, action), nil
}
// ToolBackspace implements the backspace tool call.
type ToolBackspace struct {
// Return data fields - these define the structure of data returned by this tool

View File

@@ -84,79 +84,6 @@ func (t *ToolTapXY) ConvertActionToCallToolRequest(action option.MobileAction) (
return mcp.CallToolRequest{}, fmt.Errorf("invalid tap params: %v", action.Params)
}
// ToolTap implements the tap tool call.
type ToolTap struct {
// Return data fields - these define the structure of data returned by this tool
X float64 `json:"x" desc:"X coordinate where tap was performed"`
Y float64 `json:"y" desc:"Y coordinate where tap was performed"`
}
func (t *ToolTap) Name() option.ActionName {
return option.ACTION_Tap
}
func (t *ToolTap) Description() string {
return "Tap on the screen at given relative coordinates (0.0-1.0 range)"
}
func (t *ToolTap) Options() []mcp.ToolOption {
unifiedReq := &option.ActionOptions{}
return unifiedReq.GetMCPOptions(option.ACTION_Tap)
}
func (t *ToolTap) 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()
// Validate required parameters
if unifiedReq.X == 0 || unifiedReq.Y == 0 {
return nil, fmt.Errorf("x and y coordinates are required")
}
// Tap action logic
err = driverExt.TapXY(unifiedReq.X, unifiedReq.Y, opts...)
if err != nil {
return NewMCPErrorResponse(fmt.Sprintf("Tap failed: %s", err.Error())), err
}
message := fmt.Sprintf("Successfully tapped at coordinates (%.2f, %.2f)", unifiedReq.X, unifiedReq.Y)
returnData := ToolTap{
X: unifiedReq.X,
Y: unifiedReq.Y,
}
return NewMCPSuccessResponse(message, &returnData), nil
}
}
func (t *ToolTap) ConvertActionToCallToolRequest(action option.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
}
return BuildMCPCallToolRequest(t.Name(), arguments, action), nil
}
return mcp.CallToolRequest{}, fmt.Errorf("invalid tap params: %v", action.Params)
}
// ToolTapAbsXY implements the tap_abs_xy tool call.
type ToolTapAbsXY struct {
// Return data fields - these define the structure of data returned by this tool

View File

@@ -43,9 +43,7 @@ const (
ACTION_AppClear ActionName = "app_clear"
ACTION_AppStart ActionName = "app_start"
ACTION_AppLaunch ActionName = "app_launch" // 启动 app 并堵塞等待 app 首屏加载完成
ACTION_OpenApp ActionName = "open_app" // 启动 app 并堵塞等待 app 首屏加载完成
ACTION_AppTerminate ActionName = "app_terminate"
ACTION_TerminateApp ActionName = "terminal_app"
ACTION_ColdLaunch ActionName = "cold_launch"
ACTION_AppStop ActionName = "app_stop"
ACTION_ScreenShot ActionName = "screenshot"
@@ -61,7 +59,6 @@ const (
// UI handling
ACTION_Home ActionName = "home"
ACTION_Tap ActionName = "tap" // generic tap action
ACTION_TapXY ActionName = "tap_xy"
ACTION_TapAbsXY ActionName = "tap_abs_xy"
ACTION_TapByOCR ActionName = "tap_ocr"
@@ -73,7 +70,6 @@ const (
ACTION_SwipeCoordinate ActionName = "swipe_coordinate" // swipe by coordinates (fromX, fromY, toX, toY)
ACTION_Drag ActionName = "drag"
ACTION_Input ActionName = "input"
ACTION_Text ActionName = "text"
ACTION_PressButton ActionName = "press_button"
ACTION_Back ActionName = "back"
ACTION_KeyCode ActionName = "keycode"
@@ -605,7 +601,6 @@ func WithOutputSchema(schema interface{}) ActionOption {
func (o *ActionOptions) GetMCPOptions(actionType ActionName) []mcp.ToolOption {
// Define field mappings for different action types
fieldMappings := map[ActionName][]string{
ACTION_Tap: {"platform", "serial", "x", "y", "duration"},
ACTION_TapXY: {"platform", "serial", "x", "y", "duration"},
ACTION_TapAbsXY: {"platform", "serial", "x", "y", "duration"},
ACTION_TapByOCR: {"platform", "serial", "text", "ignoreNotFoundError", "maxRetryTimes", "index", "regex", "tapRandomRect"},
@@ -616,12 +611,9 @@ func (o *ActionOptions) GetMCPOptions(actionType ActionName) []mcp.ToolOption {
ACTION_Swipe: {"platform", "serial", "direction", "fromX", "fromY", "toX", "toY", "duration", "pressDuration"},
ACTION_Drag: {"platform", "serial", "fromX", "fromY", "toX", "toY", "duration", "pressDuration"},
ACTION_Input: {"platform", "serial", "text", "frequency"},
ACTION_Text: {"platform", "serial", "text", "frequency"},
ACTION_Backspace: {"platform", "serial", "count"},
ACTION_AppLaunch: {"platform", "serial", "packageName"},
ACTION_OpenApp: {"platform", "serial", "packageName"},
ACTION_AppTerminate: {"platform", "serial", "packageName"},
ACTION_TerminateApp: {"platform", "serial", "packageName"},
ACTION_ColdLaunch: {"platform", "serial", "packageName"},
ACTION_AppInstall: {"platform", "serial", "appUrl", "packageName"},
ACTION_AppUninstall: {"platform", "serial", "packageName"},

View File

@@ -58,8 +58,8 @@ func NewXTDriver(driver IDriver, opts ...option.AIServiceOption) (*XTDriver, err
if driverExt.LLMService != nil {
mcpTools := driverExt.client.Server.ListTools()
einoTools := ai.ConvertMCPToolsToEinoToolInfos(mcpTools, "uixt")
if err := driverExt.LLMService.RegisterTools(einoTools); err != nil {
log.Warn().Err(err).Msg("failed to register uixt tools")
if err = driverExt.LLMService.RegisterTools(einoTools); err != nil {
log.Warn().Err(err).Msg("failed to register uixt tools to LLM service")
}
}