refactor: move tool request types to option

This commit is contained in:
lilong.129
2025-05-24 23:51:58 +08:00
parent f65d8aebbd
commit 97dad38b7b
7 changed files with 85 additions and 79 deletions

View File

@@ -1 +1 @@
v5.0.0-beta-2505242332
v5.0.0-beta-2505242351

View File

@@ -5,7 +5,7 @@ import (
"github.com/rs/zerolog/log"
"github.com/httprunner/httprunner/v5/uixt"
"github.com/httprunner/httprunner/v5/uixt/types"
"github.com/httprunner/httprunner/v5/uixt/option"
)
func (r *Router) foregroundAppHandler(c *gin.Context) {
@@ -22,7 +22,7 @@ func (r *Router) foregroundAppHandler(c *gin.Context) {
}
func (r *Router) appInfoHandler(c *gin.Context) {
var appInfoReq types.AppInfoRequest
var appInfoReq option.AppInfoRequest
if err := c.ShouldBindQuery(&appInfoReq); err != nil {
RenderErrorValidateRequest(c, err)
return
@@ -51,7 +51,7 @@ func (r *Router) appInfoHandler(c *gin.Context) {
}
func (r *Router) clearAppHandler(c *gin.Context) {
var appClearReq types.AppClearRequest
var appClearReq option.AppClearRequest
if err := c.ShouldBindJSON(&appClearReq); err != nil {
RenderErrorValidateRequest(c, err)
return
@@ -70,7 +70,7 @@ func (r *Router) clearAppHandler(c *gin.Context) {
}
func (r *Router) launchAppHandler(c *gin.Context) {
var appLaunchReq types.AppLaunchRequest
var appLaunchReq option.AppLaunchRequest
if err := c.ShouldBindJSON(&appLaunchReq); err != nil {
RenderErrorValidateRequest(c, err)
return
@@ -88,7 +88,7 @@ func (r *Router) launchAppHandler(c *gin.Context) {
}
func (r *Router) terminalAppHandler(c *gin.Context) {
var appTerminateReq types.AppTerminateRequest
var appTerminateReq option.AppTerminateRequest
if err := c.ShouldBindJSON(&appTerminateReq); err != nil {
RenderErrorValidateRequest(c, err)
return
@@ -106,7 +106,7 @@ func (r *Router) terminalAppHandler(c *gin.Context) {
}
func (r *Router) uninstallAppHandler(c *gin.Context) {
var appUninstallReq types.AppUninstallRequest
var appUninstallReq option.AppUninstallRequest
if err := c.ShouldBindJSON(&appUninstallReq); err != nil {
RenderErrorValidateRequest(c, err)
return

View File

@@ -4,7 +4,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/httprunner/httprunner/v5/uixt"
"github.com/httprunner/httprunner/v5/uixt/types"
"github.com/httprunner/httprunner/v5/uixt/option"
)
func (r *Router) unlockHandler(c *gin.Context) {
@@ -34,7 +34,7 @@ func (r *Router) homeHandler(c *gin.Context) {
}
func (r *Router) backspaceHandler(c *gin.Context) {
var deleteReq types.DeleteRequest
var deleteReq option.DeleteRequest
if err := c.ShouldBindJSON(&deleteReq); err != nil {
RenderErrorValidateRequest(c, err)
return
@@ -55,7 +55,7 @@ func (r *Router) backspaceHandler(c *gin.Context) {
}
func (r *Router) keycodeHandler(c *gin.Context) {
var keycodeReq types.KeycodeRequest
var keycodeReq option.KeycodeRequest
if err := c.ShouldBindJSON(&keycodeReq); err != nil {
RenderErrorValidateRequest(c, err)
return

View File

@@ -4,11 +4,10 @@ import (
"github.com/gin-gonic/gin"
"github.com/httprunner/httprunner/v5/uixt"
"github.com/httprunner/httprunner/v5/uixt/option"
"github.com/httprunner/httprunner/v5/uixt/types"
)
func (r *Router) tapHandler(c *gin.Context) {
var tapReq types.TapRequest
var tapReq option.TapRequest
if err := c.ShouldBindJSON(&tapReq); err != nil {
RenderErrorValidateRequest(c, err)
return
@@ -31,7 +30,7 @@ func (r *Router) tapHandler(c *gin.Context) {
}
func (r *Router) rightClickHandler(c *gin.Context) {
var rightClickReq types.TapRequest
var rightClickReq option.TapRequest
if err := c.ShouldBindJSON(&rightClickReq); err != nil {
RenderErrorValidateRequest(c, err)
return
@@ -118,7 +117,7 @@ func (r *Router) scrollHandler(c *gin.Context) {
}
func (r *Router) doubleTapHandler(c *gin.Context) {
var tapReq types.TapRequest
var tapReq option.TapRequest
if err := c.ShouldBindJSON(&tapReq); err != nil {
RenderErrorValidateRequest(c, err)
return
@@ -138,7 +137,7 @@ func (r *Router) doubleTapHandler(c *gin.Context) {
}
func (r *Router) dragHandler(c *gin.Context) {
var dragReq types.DragRequest
var dragReq option.DragRequest
if err := c.ShouldBindJSON(&dragReq); err != nil {
RenderErrorValidateRequest(c, err)
return
@@ -162,7 +161,7 @@ func (r *Router) dragHandler(c *gin.Context) {
}
func (r *Router) inputHandler(c *gin.Context) {
var inputReq types.InputRequest
var inputReq option.InputRequest
if err := c.ShouldBindJSON(&inputReq); err != nil {
RenderErrorValidateRequest(c, err)
return

View File

@@ -8,7 +8,7 @@ import (
"net/http/httptest"
"testing"
"github.com/httprunner/httprunner/v5/uixt/types"
"github.com/httprunner/httprunner/v5/uixt/option"
"github.com/stretchr/testify/assert"
)
@@ -18,14 +18,14 @@ func TestTapHandler(t *testing.T) {
tests := []struct {
name string
path string
tapReq types.TapRequest
tapReq option.TapRequest
wantStatus int
wantResp HttpResponse
}{
{
name: "tap abs xy",
path: fmt.Sprintf("/api/v1/android/%s/ui/tap", "4622ca24"),
tapReq: types.TapRequest{
tapReq: option.TapRequest{
X: 500,
Y: 800,
Duration: 0,
@@ -40,7 +40,7 @@ func TestTapHandler(t *testing.T) {
{
name: "tap relative xy",
path: fmt.Sprintf("/api/v1/android/%s/ui/tap", "4622ca24"),
tapReq: types.TapRequest{
tapReq: option.TapRequest{
X: 0.5,
Y: 0.6,
Duration: 0,

View File

@@ -4,7 +4,6 @@ import (
"context"
"encoding/json"
"fmt"
"reflect"
"strings"
"sync"
@@ -16,7 +15,6 @@ import (
"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"
)
// NewMCPServer creates a new MCP server for XTDriver and registers all tools.
@@ -220,7 +218,7 @@ func (t *ToolListPackages) Description() string {
}
func (t *ToolListPackages) Options() []mcp.ToolOption {
return generateMCPOptions(&types.TargetDeviceRequest{})
return option.NewMCPOptions(&option.TargetDeviceRequest{})
}
func (t *ToolListPackages) Implement() toolCall {
@@ -250,7 +248,7 @@ func (t *ToolLaunchApp) Description() string {
}
func (t *ToolLaunchApp) Options() []mcp.ToolOption {
return generateMCPOptions(&types.AppLaunchRequest{})
return option.NewMCPOptions(&option.AppLaunchRequest{})
}
func (t *ToolLaunchApp) Implement() toolCall {
@@ -259,7 +257,7 @@ func (t *ToolLaunchApp) Implement() toolCall {
if err != nil {
return nil, err
}
var appLaunchReq types.AppLaunchRequest
var appLaunchReq option.AppLaunchRequest
if err := mapToStruct(request.Params.Arguments, &appLaunchReq); err != nil {
return mcp.NewToolResultError("parse parameters error: " + err.Error()), nil
}
@@ -287,7 +285,7 @@ func (t *ToolTerminateApp) Description() string {
}
func (t *ToolTerminateApp) Options() []mcp.ToolOption {
return generateMCPOptions(&types.AppTerminateRequest{})
return option.NewMCPOptions(&option.AppTerminateRequest{})
}
func (t *ToolTerminateApp) Implement() toolCall {
@@ -296,7 +294,7 @@ func (t *ToolTerminateApp) Implement() toolCall {
if err != nil {
return nil, err
}
var appTerminateReq types.AppTerminateRequest
var appTerminateReq option.AppTerminateRequest
if err := mapToStruct(request.Params.Arguments, &appTerminateReq); err != nil {
return mcp.NewToolResultError("parse parameters error: " + err.Error()), nil
}
@@ -324,7 +322,7 @@ func (t *ToolGetScreenSize) Description() string {
}
func (t *ToolGetScreenSize) Options() []mcp.ToolOption {
return generateMCPOptions(&types.TargetDeviceRequest{})
return option.NewMCPOptions(&option.TargetDeviceRequest{})
}
func (t *ToolGetScreenSize) Implement() toolCall {
@@ -356,7 +354,7 @@ func (t *ToolPressButton) Description() string {
}
func (t *ToolPressButton) Options() []mcp.ToolOption {
return generateMCPOptions(&types.PressButtonRequest{})
return option.NewMCPOptions(&option.PressButtonRequest{})
}
func (t *ToolPressButton) Implement() toolCall {
@@ -365,7 +363,7 @@ func (t *ToolPressButton) Implement() toolCall {
if err != nil {
return nil, err
}
var pressButtonReq types.PressButtonRequest
var pressButtonReq option.PressButtonRequest
if err := mapToStruct(request.Params.Arguments, &pressButtonReq); err != nil {
return mcp.NewToolResultError("parse parameters error: " + err.Error()), nil
}
@@ -389,7 +387,7 @@ func (t *ToolTapXY) Description() string {
}
func (t *ToolTapXY) Options() []mcp.ToolOption {
return generateMCPOptions(&types.TapRequest{})
return option.NewMCPOptions(&option.TapRequest{})
}
func (t *ToolTapXY) Implement() toolCall {
@@ -398,7 +396,7 @@ func (t *ToolTapXY) Implement() toolCall {
if err != nil {
return mcp.NewToolResultError("Tap failed: " + err.Error()), nil
}
var tapReq types.TapRequest
var tapReq option.TapRequest
if err := mapToStruct(request.Params.Arguments, &tapReq); err != nil {
return mcp.NewToolResultError("parse parameters error: " + err.Error()), nil
}
@@ -426,7 +424,7 @@ func (t *ToolSwipe) Description() string {
}
func (t *ToolSwipe) Options() []mcp.ToolOption {
return generateMCPOptions(&types.SwipeRequest{})
return option.NewMCPOptions(&option.SwipeRequest{})
}
func (t *ToolSwipe) Implement() toolCall {
@@ -435,7 +433,7 @@ func (t *ToolSwipe) Implement() toolCall {
if err != nil {
return mcp.NewToolResultError("Swipe failed: " + err.Error()), nil
}
var swipeReq types.SwipeRequest
var swipeReq option.SwipeRequest
if err := mapToStruct(request.Params.Arguments, &swipeReq); err != nil {
return mcp.NewToolResultError("parse parameters error: " + err.Error()), nil
}
@@ -480,7 +478,7 @@ func (t *ToolDrag) Description() string {
}
func (t *ToolDrag) Options() []mcp.ToolOption {
return generateMCPOptions(&types.DragRequest{})
return option.NewMCPOptions(&option.DragRequest{})
}
func (t *ToolDrag) Implement() toolCall {
@@ -489,7 +487,7 @@ func (t *ToolDrag) Implement() toolCall {
if err != nil {
return nil, err
}
var dragReq types.DragRequest
var dragReq option.DragRequest
if err := mapToStruct(request.Params.Arguments, &dragReq); err != nil {
return mcp.NewToolResultError("parse parameters error: " + err.Error()), nil
}
@@ -521,7 +519,7 @@ func (t *ToolScreenShot) Description() string {
}
func (t *ToolScreenShot) Options() []mcp.ToolOption {
return generateMCPOptions(&types.TargetDeviceRequest{})
return option.NewMCPOptions(&option.TargetDeviceRequest{})
}
func (t *ToolScreenShot) Implement() toolCall {
@@ -630,46 +628,6 @@ func NewDevice(platform, serial string) (device IDevice, err error) {
return device, nil
}
// generateMCPOptions generates mcp.NewTool parameters from a struct type.
// It automatically generates mcp.NewTool parameters based on the struct fields and their desc tags.
func generateMCPOptions(t interface{}) (options []mcp.ToolOption) {
tType := reflect.TypeOf(t)
for i := 0; i < tType.NumField(); i++ {
field := tType.Field(i)
jsonTag := field.Tag.Get("json")
if jsonTag == "" || jsonTag == "-" {
continue
}
name := strings.Split(jsonTag, ",")[0]
binding := field.Tag.Get("binding")
required := strings.Contains(binding, "required")
desc := field.Tag.Get("desc")
switch field.Type.Kind() {
case reflect.Float64, reflect.Float32, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if required {
options = append(options, mcp.WithNumber(name, mcp.Required(), mcp.Description(desc)))
} else {
options = append(options, mcp.WithNumber(name, mcp.Description(desc)))
}
case reflect.String:
if required {
options = append(options, mcp.WithString(name, mcp.Required(), mcp.Description(desc)))
} else {
options = append(options, mcp.WithString(name, mcp.Description(desc)))
}
case reflect.Bool:
if required {
options = append(options, mcp.WithBoolean(name, mcp.Required(), mcp.Description(desc)))
} else {
options = append(options, mcp.WithBoolean(name, mcp.Description(desc)))
}
default:
log.Warn().Str("field_type", field.Type.String()).Msg("Unsupported field type")
}
}
return options
}
// mapToStruct convert map[string]interface{} to target struct
func mapToStruct(m map[string]interface{}, out interface{}) error {
b, err := json.Marshal(m)

View File

@@ -1,4 +1,13 @@
package types
package option
import (
"reflect"
"strings"
"github.com/httprunner/httprunner/v5/uixt/types"
"github.com/mark3labs/mcp-go/mcp"
"github.com/rs/zerolog/log"
)
type TargetDeviceRequest struct {
Platform string `json:"platform" binding:"required" desc:"Device platform: android/ios/browser"`
@@ -80,5 +89,45 @@ type AppTerminateRequest struct {
type PressButtonRequest struct {
TargetDeviceRequest
Button DeviceButton `json:"button" binding:"required" desc:"The button to press. Supported buttons: BACK (android only), HOME, VOLUME_UP, VOLUME_DOWN, ENTER."`
Button types.DeviceButton `json:"button" binding:"required" desc:"The button to press. Supported buttons: BACK (android only), HOME, VOLUME_UP, VOLUME_DOWN, ENTER."`
}
// NewMCPOptions generates mcp.NewTool parameters from a struct type.
// It automatically generates mcp.NewTool parameters based on the struct fields and their desc tags.
func NewMCPOptions(t interface{}) (options []mcp.ToolOption) {
tType := reflect.TypeOf(t)
for i := 0; i < tType.NumField(); i++ {
field := tType.Field(i)
jsonTag := field.Tag.Get("json")
if jsonTag == "" || jsonTag == "-" {
continue
}
name := strings.Split(jsonTag, ",")[0]
binding := field.Tag.Get("binding")
required := strings.Contains(binding, "required")
desc := field.Tag.Get("desc")
switch field.Type.Kind() {
case reflect.Float64, reflect.Float32, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if required {
options = append(options, mcp.WithNumber(name, mcp.Required(), mcp.Description(desc)))
} else {
options = append(options, mcp.WithNumber(name, mcp.Description(desc)))
}
case reflect.String:
if required {
options = append(options, mcp.WithString(name, mcp.Required(), mcp.Description(desc)))
} else {
options = append(options, mcp.WithString(name, mcp.Description(desc)))
}
case reflect.Bool:
if required {
options = append(options, mcp.WithBoolean(name, mcp.Required(), mcp.Description(desc)))
} else {
options = append(options, mcp.WithBoolean(name, mcp.Description(desc)))
}
default:
log.Warn().Str("field_type", field.Type.String()).Msg("Unsupported field type")
}
}
return options
}