From 8b20fceb63f10ea27808f3d33a437fdc3d192978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E6=B3=93=E9=93=AE?= Date: Thu, 24 Jul 2025 17:26:25 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E5=89=AA=E8=B4=B4=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- uixt/android_driver_uia2.go | 16 ++++------- uixt/browser_driver.go | 5 +++- uixt/driver.go | 3 ++ uixt/harmony_driver_hdc.go | 4 +++ uixt/ios_driver_wda.go | 11 +++---- uixt/mcp_tools_pasteboard.go | 56 ++++++++++++++++++++++++++++++++++++ uixt/option/action.go | 3 +- 7 files changed, 81 insertions(+), 17 deletions(-) create mode 100644 uixt/mcp_tools_pasteboard.go diff --git a/uixt/android_driver_uia2.go b/uixt/android_driver_uia2.go index 70390860..7e7776e1 100644 --- a/uixt/android_driver_uia2.go +++ b/uixt/android_driver_uia2.go @@ -574,30 +574,26 @@ func (ud *UIA2Driver) SetPasteboard(contentType types.PasteboardType, content st return } -func (ud *UIA2Driver) GetPasteboard(contentType types.PasteboardType) (raw *bytes.Buffer, err error) { - if len(contentType) == 0 { - contentType = types.PasteboardTypePlaintext - } +func (ud *UIA2Driver) GetPasteboard() (content string, err error) { // register(postHandler, new GetClipboard("/wd/hub/session/:sessionId/appium/device/get_clipboard")) data := map[string]interface{}{ - "contentType": contentType[0], + "contentType": types.PasteboardTypePlaintext, } var rawResp DriverRawResponse urlStr := fmt.Sprintf("/session/%s/appium/device/get_clipboard", ud.Session.ID) if rawResp, err = ud.Session.POST(data, urlStr); err != nil { - return + return "", err } reply := new(struct{ Value string }) if err = json.Unmarshal(rawResp, reply); err != nil { - return + return "", err } if data, err := base64.StdEncoding.DecodeString(reply.Value); err != nil { - raw.Write([]byte(reply.Value)) + return reply.Value, nil } else { - raw.Write(data) + return string(data), nil } - return } // SendKeys Android input does not support setting frequency. diff --git a/uixt/browser_driver.go b/uixt/browser_driver.go index 28820a81..6a98bcf5 100644 --- a/uixt/browser_driver.go +++ b/uixt/browser_driver.go @@ -365,7 +365,6 @@ func (wd *BrowserDriver) Input(text string, option ...option.ActionOption) (err // Source Return application elements tree func (wd *BrowserDriver) Source(srcOpt ...option.SourceOption) (string, error) { result, err := wd.CustomeGet(wd.concatURL(wd.Session.ID, "stub/source")) - if err != nil { return "", err } @@ -751,3 +750,7 @@ func (wd *BrowserDriver) CustomeGet(urlStr string) (response *WebAgentResponse, return &webResp, err } + +func (wd *BrowserDriver) GetPasteboard() (content string, err error) { + return "", errors.New("not implemented") +} diff --git a/uixt/driver.go b/uixt/driver.go index 718ed821..58d31fc6 100644 --- a/uixt/driver.go +++ b/uixt/driver.go @@ -86,4 +86,7 @@ type IDriver interface { // triggers the log capture and returns the log entries StartCaptureLog(identifier ...string) error StopCaptureLog() (result interface{}, err error) + + // clipboard operations + GetPasteboard() (string, error) } diff --git a/uixt/harmony_driver_hdc.go b/uixt/harmony_driver_hdc.go index c4c93e92..398251cb 100644 --- a/uixt/harmony_driver_hdc.go +++ b/uixt/harmony_driver_hdc.go @@ -348,3 +348,7 @@ func (hd *HDCDriver) SecondaryClick(x, y float64) (err error) { func (hd *HDCDriver) SecondaryClickBySelector(selector string, options ...option.ActionOption) (err error) { return err } + +func (hd *HDCDriver) GetPasteboard() (content string, err error) { + return "", errors.New("not implemented") +} diff --git a/uixt/ios_driver_wda.go b/uixt/ios_driver_wda.go index 02be5bac..9042c736 100644 --- a/uixt/ios_driver_wda.go +++ b/uixt/ios_driver_wda.go @@ -630,21 +630,22 @@ func (wd *WDADriver) SetPasteboard(contentType types.PasteboardType, content str return } -func (wd *WDADriver) GetPasteboard() (raw *bytes.Buffer, err error) { +func (wd *WDADriver) GetPasteboard() (content string, err error) { // [[FBRoute POST:@"/wda/getPasteboard"] respondWithTarget:self action:@selector(handleGetPasteboard:)] err = wd.AppLaunch("com.gtf.wda.runner.xctrunner") if err != nil { - return nil, errors.Wrap(err, "GetPasteboard failed. WDA app not launched") + return "", errors.Wrap(err, "GetPasteboard failed. WDA app not launched") } data := map[string]interface{}{} var rawResp DriverRawResponse if rawResp, err = wd.Session.POST(data, "/wda/getPasteboard"); err != nil { - return nil, err + return "", err } + var raw *bytes.Buffer if raw, err = rawResp.ValueDecodeAsBase64(); err != nil { - return nil, err + return "", err } - return + return string(raw.Bytes()), nil } func (wd *WDADriver) SetIme(ime string) error { diff --git a/uixt/mcp_tools_pasteboard.go b/uixt/mcp_tools_pasteboard.go new file mode 100644 index 00000000..4fb1c406 --- /dev/null +++ b/uixt/mcp_tools_pasteboard.go @@ -0,0 +1,56 @@ +package uixt + +import ( + "context" + "fmt" + + "github.com/mark3labs/mcp-go/mcp" + "github.com/mark3labs/mcp-go/server" + + "github.com/httprunner/httprunner/v5/uixt/option" +) + +// ToolGetPasteboard implements the get_pasteboard tool call. +type ToolGetPasteboard struct { + // Return data fields - these define the structure of data returned by this tool + Content string `json:"content" desc:"Clipboard content that was retrieved"` +} + +func (t *ToolGetPasteboard) Name() option.ActionName { + return option.ACTION_GetPasteboard +} + +func (t *ToolGetPasteboard) Description() string { + return "Get the clipboard content from the device" +} + +func (t *ToolGetPasteboard) Options() []mcp.ToolOption { + unifiedReq := &option.ActionOptions{} + return unifiedReq.GetMCPOptions(option.ACTION_GetPasteboard) +} + +func (t *ToolGetPasteboard) 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) + } + + // Directly call the GetPasteboard method on the driver + content, err := driverExt.IDriver.GetPasteboard() + if err != nil { + return NewMCPErrorResponse(fmt.Sprintf("Get pasteboard failed: %s", err.Error())), err + } + + message := "Successfully retrieved clipboard content" + returnData := ToolGetPasteboard{Content: content} + + return NewMCPSuccessResponse(message, &returnData), nil + } +} + +func (t *ToolGetPasteboard) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) { + arguments := map[string]any{} + return BuildMCPCallToolRequest(t.Name(), arguments, action), nil +} diff --git a/uixt/option/action.go b/uixt/option/action.go index edbffc31..a6f40736 100644 --- a/uixt/option/action.go +++ b/uixt/option/action.go @@ -55,7 +55,8 @@ const ( ACTION_SetIme ActionName = "set_ime" ACTION_GetSource ActionName = "get_source" ACTION_GetForegroundApp ActionName = "get_foreground_app" - ACTION_AppInfo ActionName = "app_info" // get app info action + ACTION_GetPasteboard ActionName = "get_pasteboard" // get clipboard content + ACTION_AppInfo ActionName = "app_info" // get app info action // UI handling ACTION_Home ActionName = "home"