refactor: move uixt mcp server

This commit is contained in:
lilong.129
2025-05-24 10:46:50 +08:00
parent 014140ccc7
commit 0a68701faa
4 changed files with 52 additions and 53 deletions
+2 -2
View File
@@ -1,7 +1,7 @@
package cmd package cmd
import ( import (
"github.com/httprunner/httprunner/v5/mcphost" "github.com/httprunner/httprunner/v5/uixt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@@ -10,7 +10,7 @@ var CmdMCPServer = &cobra.Command{
Short: "Start MCP server for UI automation", Short: "Start MCP server for UI automation",
Long: `Start MCP server for UI automation, expose device driver via MCP protocol`, Long: `Start MCP server for UI automation, expose device driver via MCP protocol`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
mcpServer := mcphost.NewMCPServer() mcpServer := uixt.NewMCPServer()
return mcpServer.Start() return mcpServer.Start()
}, },
} }
+1 -1
View File
@@ -1 +1 @@
v5.0.0-beta-2505241028 v5.0.0-beta-2505241046
+2 -2
View File
@@ -69,8 +69,8 @@ func (h *MCPHost) InitServers(ctx context.Context) error {
// initialize uixt MCP server // initialize uixt MCP server
if h.withUIXT { if h.withUIXT {
h.connections["uixt"] = &Connection{ h.connections["uixt"] = &Connection{
Client: &MCPClient4XTDriver{ Client: &uixt.MCPClient4XTDriver{
server: NewMCPServer(), Server: uixt.NewMCPServer(),
}, },
Config: nil, Config: nil,
} }
+47 -48
View File
@@ -1,4 +1,4 @@
package mcphost package uixt
import ( import (
"context" "context"
@@ -11,7 +11,6 @@ import (
"github.com/danielpaulus/go-ios/ios" "github.com/danielpaulus/go-ios/ios"
"github.com/httprunner/httprunner/v5/internal/version" "github.com/httprunner/httprunner/v5/internal/version"
"github.com/httprunner/httprunner/v5/pkg/gadb" "github.com/httprunner/httprunner/v5/pkg/gadb"
"github.com/httprunner/httprunner/v5/uixt"
"github.com/httprunner/httprunner/v5/uixt/option" "github.com/httprunner/httprunner/v5/uixt/option"
"github.com/httprunner/httprunner/v5/uixt/types" "github.com/httprunner/httprunner/v5/uixt/types"
"github.com/mark3labs/mcp-go/client" "github.com/mark3labs/mcp-go/client"
@@ -23,16 +22,16 @@ import (
// MCPClient4XTDriver is a minimal MCP client that only implements the methods used by the host // MCPClient4XTDriver is a minimal MCP client that only implements the methods used by the host
type MCPClient4XTDriver struct { type MCPClient4XTDriver struct {
client.MCPClient client.MCPClient
server *MCPServer4XTDriver Server *MCPServer4XTDriver
} }
func (c *MCPClient4XTDriver) ListTools(ctx context.Context, req mcp.ListToolsRequest) (*mcp.ListToolsResult, error) { func (c *MCPClient4XTDriver) ListTools(ctx context.Context, req mcp.ListToolsRequest) (*mcp.ListToolsResult, error) {
tools := c.server.ListTools() tools := c.Server.ListTools()
return &mcp.ListToolsResult{Tools: tools}, nil return &mcp.ListToolsResult{Tools: tools}, nil
} }
func (c *MCPClient4XTDriver) CallTool(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func (c *MCPClient4XTDriver) CallTool(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
handler := c.server.GetHandler(req.Params.Name) handler := c.Server.GetHandler(req.Params.Name)
if handler == nil { if handler == nil {
return mcp.NewToolResultError(fmt.Sprintf("handler for tool %s not found", req.Params.Name)), nil return mcp.NewToolResultError(fmt.Sprintf("handler for tool %s not found", req.Params.Name)), nil
} }
@@ -49,16 +48,6 @@ func (c *MCPClient4XTDriver) Close() error {
return nil return nil
} }
type toolCall = func(context.Context, mcp.CallToolRequest) (*mcp.CallToolResult, error)
// MCPServer4XTDriver wraps a MCPServer to expose XTDriver functionality via MCP protocol.
type MCPServer4XTDriver struct {
mcpServer *server.MCPServer
driverCache sync.Map // key is serial, value is *XTDriver
tools []mcp.Tool // tools list for uixt
handlerMap map[string]toolCall // tool name to handler
}
// NewMCPServer creates a new MCP server for XTDriver and registers all tools. // NewMCPServer creates a new MCP server for XTDriver and registers all tools.
func NewMCPServer() *MCPServer4XTDriver { func NewMCPServer() *MCPServer4XTDriver {
mcpServer := server.NewMCPServer( mcpServer := server.NewMCPServer(
@@ -74,12 +63,45 @@ func NewMCPServer() *MCPServer4XTDriver {
return s return s
} }
type toolCall = func(context.Context, mcp.CallToolRequest) (*mcp.CallToolResult, error)
// MCPServer4XTDriver wraps a MCPServer to expose XTDriver functionality via MCP protocol.
type MCPServer4XTDriver struct {
mcpServer *server.MCPServer
driverCache sync.Map // key is serial, value is *XTDriver
tools []mcp.Tool // tools list for uixt
handlerMap map[string]toolCall // tool name to handler
}
// Start runs the MCP server (blocking). // Start runs the MCP server (blocking).
func (s *MCPServer4XTDriver) Start() error { func (s *MCPServer4XTDriver) Start() error {
log.Info().Msg("Starting HttpRunner UIXT MCP Server...") log.Info().Msg("Starting HttpRunner UIXT MCP Server...")
return server.ServeStdio(s.mcpServer) return server.ServeStdio(s.mcpServer)
} }
// ListTools returns all registered tools
func (s *MCPServer4XTDriver) ListTools() []mcp.Tool {
return s.tools
}
// GetTool returns a pointer to the mcp.Tool with the given name
func (s *MCPServer4XTDriver) GetTool(name string) *mcp.Tool {
for i := range s.tools {
if s.tools[i].Name == name {
return &s.tools[i]
}
}
return nil
}
// GetHandler returns the tool handler for the given name
func (s *MCPServer4XTDriver) GetHandler(name string) toolCall {
if s.handlerMap == nil {
return nil
}
return s.handlerMap[name]
}
// addTools registers all MCP tools. // addTools registers all MCP tools.
func (ums *MCPServer4XTDriver) addTools() { func (ums *MCPServer4XTDriver) addTools() {
// ListAvailableDevices Tool // ListAvailableDevices Tool
@@ -203,7 +225,7 @@ func (ums *MCPServer4XTDriver) handleListAvailableDevices(ctx context.Context, r
if iosDevices, err := ios.ListDevices(); err == nil { if iosDevices, err := ios.ListDevices(); err == nil {
serialList := make([]string, 0, len(iosDevices.DeviceList)) serialList := make([]string, 0, len(iosDevices.DeviceList))
for _, dev := range iosDevices.DeviceList { for _, dev := range iosDevices.DeviceList {
device, err := uixt.NewIOSDevice( device, err := NewIOSDevice(
option.WithUDID(dev.Properties.SerialNumber)) option.WithUDID(dev.Properties.SerialNumber))
if err != nil { if err != nil {
continue continue
@@ -414,7 +436,7 @@ func (ums *MCPServer4XTDriver) handleScreenShot(ctx context.Context, request mcp
return nil, err return nil, err
} }
bufferBase64, err := uixt.GetScreenShotBufferBase64(driverExt.IDriver) bufferBase64, err := GetScreenShotBufferBase64(driverExt.IDriver)
if err != nil { if err != nil {
log.Error().Err(err).Msg("ScreenShot failed") log.Error().Err(err).Msg("ScreenShot failed")
return mcp.NewToolResultError(fmt.Sprintf("Failed to take screenshot: %v", err)), nil return mcp.NewToolResultError(fmt.Sprintf("Failed to take screenshot: %v", err)), nil
@@ -425,7 +447,7 @@ func (ums *MCPServer4XTDriver) handleScreenShot(ctx context.Context, request mcp
} }
// setupXTDriver initializes an XTDriver based on the platform and serial. // setupXTDriver initializes an XTDriver based on the platform and serial.
func (ums *MCPServer4XTDriver) setupXTDriver(_ context.Context, args map[string]interface{}) (*uixt.XTDriver, error) { func (ums *MCPServer4XTDriver) setupXTDriver(_ context.Context, args map[string]interface{}) (*XTDriver, error) {
platform, _ := args["platform"].(string) platform, _ := args["platform"].(string)
serial, _ := args["serial"].(string) serial, _ := args["serial"].(string)
if platform == "" { if platform == "" {
@@ -436,7 +458,7 @@ func (ums *MCPServer4XTDriver) setupXTDriver(_ context.Context, args map[string]
// Check if driver exists in cache // Check if driver exists in cache
cacheKey := fmt.Sprintf("%s_%s", platform, serial) cacheKey := fmt.Sprintf("%s_%s", platform, serial)
if cachedDriver, ok := ums.driverCache.Load(cacheKey); ok { if cachedDriver, ok := ums.driverCache.Load(cacheKey); ok {
if driverExt, ok := cachedDriver.(*uixt.XTDriver); ok { if driverExt, ok := cachedDriver.(*XTDriver); ok {
log.Info().Str("platform", platform).Str("serial", serial).Msg("Using cached driver") log.Info().Str("platform", platform).Str("serial", serial).Msg("Using cached driver")
return driverExt, nil return driverExt, nil
} }
@@ -451,22 +473,22 @@ func (ums *MCPServer4XTDriver) setupXTDriver(_ context.Context, args map[string]
return driverExt, nil return driverExt, nil
} }
func initDriverExt(platform, serial string) (*uixt.XTDriver, error) { func initDriverExt(platform, serial string) (*XTDriver, error) {
// init device // init device
var device uixt.IDevice var device IDevice
var err error var err error
switch strings.ToLower(platform) { switch strings.ToLower(platform) {
case "android": case "android":
device, err = uixt.NewAndroidDevice(option.WithSerialNumber(serial)) device, err = NewAndroidDevice(option.WithSerialNumber(serial))
case "ios": case "ios":
device, err = uixt.NewIOSDevice( device, err = NewIOSDevice(
option.WithUDID(serial), option.WithUDID(serial),
option.WithWDAPort(8700), option.WithWDAPort(8700),
option.WithWDAMjpegPort(8800), option.WithWDAMjpegPort(8800),
option.WithResetHomeOnStartup(false), option.WithResetHomeOnStartup(false),
) )
case "browser": case "browser":
device, err = uixt.NewBrowserDevice(option.WithBrowserID(serial)) device, err = NewBrowserDevice(option.WithBrowserID(serial))
default: default:
return nil, fmt.Errorf("invalid platform: %s", platform) return nil, fmt.Errorf("invalid platform: %s", platform)
} }
@@ -487,7 +509,7 @@ func initDriverExt(platform, serial string) (*uixt.XTDriver, error) {
} }
// init XTDriver // init XTDriver
driverExt, err := uixt.NewXTDriver(driver, driverExt, err := NewXTDriver(driver,
option.WithCVService(option.CVServiceTypeVEDEM)) option.WithCVService(option.CVServiceTypeVEDEM))
if err != nil { if err != nil {
return nil, fmt.Errorf("init XT driver failed: %w", err) return nil, fmt.Errorf("init XT driver failed: %w", err)
@@ -549,26 +571,3 @@ var commonToolOptions = []mcp.ToolOption{
mcp.WithString("platform", mcp.Required(), mcp.Description("Device platform: android/ios/browser")), mcp.WithString("platform", mcp.Required(), mcp.Description("Device platform: android/ios/browser")),
mcp.WithString("serial", mcp.Required(), mcp.Description("Device serial/udid/browser id")), mcp.WithString("serial", mcp.Required(), mcp.Description("Device serial/udid/browser id")),
} }
// ListTools returns all registered tools
func (s *MCPServer4XTDriver) ListTools() []mcp.Tool {
return s.tools
}
// GetTool returns a pointer to the mcp.Tool with the given name
func (s *MCPServer4XTDriver) GetTool(name string) *mcp.Tool {
for i := range s.tools {
if s.tools[i].Name == name {
return &s.tools[i]
}
}
return nil
}
// GetHandler returns the tool handler for the given name
func (s *MCPServer4XTDriver) GetHandler(name string) toolCall {
if s.handlerMap == nil {
return nil
}
return s.handlerMap[name]
}