feat: load uixt mcp server in mcphost

This commit is contained in:
lilong.129
2025-05-20 17:07:33 +08:00
parent 5066c64368
commit 83434cca1e
4 changed files with 85 additions and 10 deletions
+16
View File
@@ -0,0 +1,16 @@
package cmd
import (
"github.com/httprunner/httprunner/v5/mcphost"
"github.com/spf13/cobra"
)
var CmdMCPServer = &cobra.Command{
Use: "mcp-server",
Short: "Start MCP server for UI automation",
Long: `Start MCP server for UI automation, expose device driver via MCP protocol`,
RunE: func(cmd *cobra.Command, args []string) error {
mcpServer := mcphost.NewMCPServer()
return mcpServer.Start()
},
}
+1 -1
View File
@@ -1 +1 @@
v5.0.0-beta-2505201421 v5.0.0-beta-2505201707
+18 -4
View File
@@ -61,6 +61,14 @@ func NewMCPHost(configPath string) (*MCPHost, error) {
// InitServers initializes all MCP servers // InitServers initializes all MCP servers
func (h *MCPHost) InitServers(ctx context.Context) error { func (h *MCPHost) InitServers(ctx context.Context) error {
// initialize uixt MCP server
h.connections["uixt"] = &Connection{
Client: &MCPClient4XTDriver{
server: NewMCPServer(),
},
Config: nil,
}
for name, server := range h.config.MCPServers { for name, server := range h.config.MCPServers {
if server.Config.IsDisabled() { if server.Config.IsDisabled() {
continue continue
@@ -299,27 +307,33 @@ func (h *MCPHost) GetEinoToolInfos(ctx context.Context) ([]*schema.ToolInfo, err
var tools []*schema.ToolInfo var tools []*schema.ToolInfo
for _, serverTools := range results { for _, serverTools := range results {
if serverTools.Err != nil { if serverTools.Err != nil {
log.Error().Err(serverTools.Err).Str("server", serverTools.ServerName).Msg("failed to get tools") log.Error().Err(serverTools.Err).
Str("server", serverTools.ServerName).Msg("failed to get tools")
continue continue
} }
var toolNames []string
for _, tool := range serverTools.Tools { for _, tool := range serverTools.Tools {
einoTool, err := h.GetEinoTool(ctx, serverTools.ServerName, tool.Name) einoTool, err := h.GetEinoTool(ctx, serverTools.ServerName, tool.Name)
if err != nil { if err != nil {
log.Error().Err(err).Str("server", serverTools.ServerName).Str("tool", tool.Name).Msg("failed to get eino tool") log.Error().Err(err).Str("server", serverTools.ServerName).
Str("tool", tool.Name).Msg("failed to get eino tool")
continue continue
} }
einoToolInfo, err := einoTool.Info(ctx) einoToolInfo, err := einoTool.Info(ctx)
if err != nil { if err != nil {
log.Error().Err(err).Str("server", serverTools.ServerName).Str("tool", tool.Name).Msg("failed to get eino tool info") log.Error().Err(err).Str("server", serverTools.ServerName).
Str("tool", tool.Name).Msg("failed to get eino tool info")
continue continue
} }
einoToolInfo.Name = fmt.Sprintf("%s__%s", serverTools.ServerName, tool.Name) einoToolInfo.Name = fmt.Sprintf("%s__%s", serverTools.ServerName, tool.Name)
tools = append(tools, einoToolInfo) tools = append(tools, einoToolInfo)
toolNames = append(toolNames, tool.Name)
} }
log.Debug().Str("server", serverTools.ServerName).
Strs("tools", toolNames).Msg("loaded MCP tools")
} }
log.Info().Int("count", len(tools)).Msg("eino tool infos loaded")
return tools, nil return tools, nil
} }
+50 -5
View File
@@ -13,16 +13,49 @@ import (
"github.com/httprunner/httprunner/v5/uixt" "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/mcp" "github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server" "github.com/mark3labs/mcp-go/server"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
// MCPClient4XTDriver is a minimal MCP client that only implements the methods used by the host
type MCPClient4XTDriver struct {
client.MCPClient
server *MCPServer4XTDriver
}
func (c *MCPClient4XTDriver) ListTools(ctx context.Context, req mcp.ListToolsRequest) (*mcp.ListToolsResult, error) {
tools := c.server.ListTools()
return &mcp.ListToolsResult{Tools: tools}, nil
}
func (c *MCPClient4XTDriver) CallTool(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
handler := c.server.GetHandler(req.Params.Name)
if handler == nil {
return mcp.NewToolResultError(fmt.Sprintf("handler for tool %s not found", req.Params.Name)), nil
}
return handler(ctx, req)
}
func (c *MCPClient4XTDriver) Initialize(ctx context.Context, req mcp.InitializeRequest) (*mcp.InitializeResult, error) {
// no need to initialize for local server
return &mcp.InitializeResult{}, nil
}
func (c *MCPClient4XTDriver) Close() error {
// no need to close for local server
return nil
}
type toolCall = func(context.Context, mcp.CallToolRequest) (*mcp.CallToolResult, error)
// MCPServer4XTDriver wraps a MCPServer to expose XTDriver functionality via MCP protocol. // MCPServer4XTDriver wraps a MCPServer to expose XTDriver functionality via MCP protocol.
type MCPServer4XTDriver struct { type MCPServer4XTDriver struct {
mcpServer *server.MCPServer mcpServer *server.MCPServer
driverCache sync.Map // key is serial, value is *XTDriver driverCache sync.Map // key is serial, value is *XTDriver
tools []mcp.Tool // 本地维护的工具列表 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.
@@ -33,7 +66,8 @@ func NewMCPServer() *MCPServer4XTDriver {
server.WithToolCapabilities(false), server.WithToolCapabilities(false),
) )
s := &MCPServer4XTDriver{ s := &MCPServer4XTDriver{
mcpServer: mcpServer, mcpServer: mcpServer,
handlerMap: make(map[string]toolCall),
} }
s.addTools() s.addTools()
return s return s
@@ -56,6 +90,7 @@ func (ums *MCPServer4XTDriver) addTools() {
tapXYTool := mcp.NewTool("tap_xy", tapParams...) tapXYTool := mcp.NewTool("tap_xy", tapParams...)
ums.mcpServer.AddTool(tapXYTool, ums.handleTapXY) ums.mcpServer.AddTool(tapXYTool, ums.handleTapXY)
ums.tools = append(ums.tools, tapXYTool) ums.tools = append(ums.tools, tapXYTool)
ums.handlerMap[tapXYTool.Name] = ums.handleTapXY
log.Info().Str("name", tapXYTool.Name).Msg("Register tool") log.Info().Str("name", tapXYTool.Name).Msg("Register tool")
// Swipe Tool // Swipe Tool
@@ -67,6 +102,7 @@ func (ums *MCPServer4XTDriver) addTools() {
swipeTool := mcp.NewTool("swipe", swipeParams...) swipeTool := mcp.NewTool("swipe", swipeParams...)
ums.mcpServer.AddTool(swipeTool, ums.handleSwipe) ums.mcpServer.AddTool(swipeTool, ums.handleSwipe)
ums.tools = append(ums.tools, swipeTool) ums.tools = append(ums.tools, swipeTool)
ums.handlerMap[swipeTool.Name] = ums.handleSwipe
log.Info().Str("name", swipeTool.Name).Msg("Register tool") log.Info().Str("name", swipeTool.Name).Msg("Register tool")
// ScreenShot Tool // ScreenShot Tool
@@ -75,6 +111,7 @@ func (ums *MCPServer4XTDriver) addTools() {
) )
ums.mcpServer.AddTool(screenShotTool, ums.handleScreenShot) ums.mcpServer.AddTool(screenShotTool, ums.handleScreenShot)
ums.tools = append(ums.tools, screenShotTool) ums.tools = append(ums.tools, screenShotTool)
ums.handlerMap[screenShotTool.Name] = ums.handleScreenShot
log.Info().Str("name", screenShotTool.Name).Msg("Register tool") log.Info().Str("name", screenShotTool.Name).Msg("Register tool")
} }
@@ -259,12 +296,12 @@ var commonToolOptions = []mcp.ToolOption{
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 返回所有注册的 mcp.Tool // ListTools returns all registered tools
func (s *MCPServer4XTDriver) ListTools() []mcp.Tool { func (s *MCPServer4XTDriver) ListTools() []mcp.Tool {
return s.tools return s.tools
} }
// GetTool 根据名称返回 mcp.Tool 指针 // GetTool returns a pointer to the mcp.Tool with the given name
func (s *MCPServer4XTDriver) GetTool(name string) *mcp.Tool { func (s *MCPServer4XTDriver) GetTool(name string) *mcp.Tool {
for i := range s.tools { for i := range s.tools {
if s.tools[i].Name == name { if s.tools[i].Name == name {
@@ -273,3 +310,11 @@ func (s *MCPServer4XTDriver) GetTool(name string) *mcp.Tool {
} }
return nil 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]
}