From 533c1f4bff15d74940e31789afcb8bf2c1213d0e Mon Sep 17 00:00:00 2001 From: "lilong.129" Date: Mon, 9 Jun 2025 17:18:26 +0800 Subject: [PATCH] feat: add mcp tool ToolScreenRecord --- internal/version/VERSION | 2 +- uixt/mcp_server.go | 1 + uixt/mcp_server_test.go | 1 + uixt/mcp_tools_device.go | 90 ++++++++++++++++++++++++++++++++++++++++ uixt/option/action.go | 2 + 5 files changed, 95 insertions(+), 1 deletion(-) diff --git a/internal/version/VERSION b/internal/version/VERSION index 69e16753..f4ab5623 100644 --- a/internal/version/VERSION +++ b/internal/version/VERSION @@ -1 +1 @@ -v5.0.0-beta-2506091704 +v5.0.0-beta-2506091718 diff --git a/uixt/mcp_server.go b/uixt/mcp_server.go index 6b51bd88..613d14f6 100644 --- a/uixt/mcp_server.go +++ b/uixt/mcp_server.go @@ -106,6 +106,7 @@ func (s *MCPServer4XTDriver) registerTools() { // Screen Tools s.registerTool(&ToolScreenShot{}) + s.registerTool(&ToolScreenRecord{}) s.registerTool(&ToolGetScreenSize{}) s.registerTool(&ToolGetSource{}) diff --git a/uixt/mcp_server_test.go b/uixt/mcp_server_test.go index e7088aa7..579c75ee 100644 --- a/uixt/mcp_server_test.go +++ b/uixt/mcp_server_test.go @@ -36,6 +36,7 @@ func TestNewMCPServer(t *testing.T) { "drag", "input", "screenshot", + "screenrecord", "get_screen_size", "press_button", "home", diff --git a/uixt/mcp_tools_device.go b/uixt/mcp_tools_device.go index 587e291e..2d44cad2 100644 --- a/uixt/mcp_tools_device.go +++ b/uixt/mcp_tools_device.go @@ -124,3 +124,93 @@ func (t *ToolSelectDevice) Implement() server.ToolHandlerFunc { func (t *ToolSelectDevice) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { return buildMCPCallToolRequest(t.Name(), map[string]any{}), nil } + +// ToolScreenRecord implements the screenrecord tool call. +type ToolScreenRecord struct { + // Return data fields - these define the structure of data returned by this tool + VideoPath string `json:"videoPath" desc:"Path to the recorded video file"` + Duration float64 `json:"duration" desc:"Duration of the recording in seconds"` + Method string `json:"method" desc:"Recording method used (adb or scrcpy)"` +} + +func (t *ToolScreenRecord) Name() option.ActionName { + return option.ACTION_ScreenRecord +} + +func (t *ToolScreenRecord) Description() string { + return "Record the screen of the mobile device. Supports both ADB screenrecord and scrcpy recording methods. ADB recording is limited to 180 seconds, while scrcpy supports longer recordings and audio capture on Android 11+." +} + +func (t *ToolScreenRecord) Options() []mcp.ToolOption { + return []mcp.ToolOption{ + mcp.WithString("platform", mcp.Enum("android", "ios"), mcp.Description("The platform type of device to record")), + mcp.WithString("serial", mcp.Description("The device serial number or UDID")), + mcp.WithNumber("duration", mcp.Description("Recording duration in seconds. If not specified, recording will continue until manually stopped. ADB recording is limited to 180 seconds.")), + mcp.WithString("screenRecordPath", mcp.Description("Custom path for the output video file. If not specified, a timestamped filename will be generated.")), + mcp.WithBoolean("screenRecordWithAudio", mcp.Description("Enable audio recording (requires scrcpy and Android 11+). Default: false")), + mcp.WithBoolean("screenRecordWithScrcpy", mcp.Description("Force use of scrcpy for recording instead of ADB. Default: false (auto-detect based on audio requirement)")), + } +} + +func (t *ToolScreenRecord) 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 + } + + // Parse options from arguments + var opts []option.ActionOption + + if duration, ok := request.Params.Arguments["duration"].(float64); ok && duration > 0 { + opts = append(opts, option.WithDuration(duration)) + } + + if path, ok := request.Params.Arguments["screenRecordPath"].(string); ok && path != "" { + opts = append(opts, option.WithScreenRecordPath(path)) + } + + if audio, ok := request.Params.Arguments["screenRecordWithAudio"].(bool); ok && audio { + opts = append(opts, option.WithScreenRecordAudio(true)) + } + + if scrcpy, ok := request.Params.Arguments["screenRecordWithScrcpy"].(bool); ok && scrcpy { + opts = append(opts, option.WithScreenRecordScrcpy(true)) + } + + // Add context to options for proper cancellation handling + opts = append(opts, option.WithContext(ctx)) + + // Start screen recording + videoPath, err := driverExt.IDriver.ScreenRecord(opts...) + if err != nil { + log.Error().Err(err).Msg("ScreenRecord failed") + return NewMCPErrorResponse("Failed to record screen: " + err.Error()), nil + } + + // Determine recording method and duration + options := option.NewActionOptions(opts...) + method := "adb" + duration := options.Duration + if options.ScreenRecordDuration > 0 { + duration = options.ScreenRecordDuration + } + + if options.ScreenRecordWithScrcpy || options.ScreenRecordWithAudio { + method = "scrcpy" + } + + message := fmt.Sprintf("Screen recording completed successfully. Video saved to: %s", videoPath) + returnData := ToolScreenRecord{ + VideoPath: videoPath, + Duration: duration, + Method: method, + } + + return NewMCPSuccessResponse(message, &returnData), nil + } +} + +func (t *ToolScreenRecord) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { + return buildMCPCallToolRequest(t.Name(), map[string]any{}), nil +} diff --git a/uixt/option/action.go b/uixt/option/action.go index 56ebaa28..5ecb6bcb 100644 --- a/uixt/option/action.go +++ b/uixt/option/action.go @@ -46,6 +46,7 @@ const ( ACTION_AppTerminate ActionName = "app_terminate" ACTION_AppStop ActionName = "app_stop" ACTION_ScreenShot ActionName = "screenshot" + ACTION_ScreenRecord ActionName = "screenrecord" ACTION_GetScreenSize ActionName = "get_screen_size" ACTION_Sleep ActionName = "sleep" ACTION_SleepMS ActionName = "sleep_ms" @@ -777,6 +778,7 @@ func (o *ActionOptions) GetMCPOptions(actionType ActionName) []mcp.ToolOption { ACTION_ListAvailableDevices: {}, ACTION_SelectDevice: {"platform", "serial"}, ACTION_ScreenShot: {"platform", "serial"}, + ACTION_ScreenRecord: {"platform", "serial", "duration", "screenRecordPath", "screenRecordWithAudio", "screenRecordWithScrcpy"}, ACTION_GetScreenSize: {"platform", "serial"}, ACTION_Home: {"platform", "serial"}, ACTION_Back: {"platform", "serial"},