mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-10 17:43:00 +08:00
feat: load uixt mcp server in mcphost
This commit is contained in:
16
cmd/mcpserver.go
Normal file
16
cmd/mcpserver.go
Normal 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 @@
|
||||
v5.0.0-beta-2505201421
|
||||
v5.0.0-beta-2505201707
|
||||
|
||||
@@ -61,6 +61,14 @@ func NewMCPHost(configPath string) (*MCPHost, error) {
|
||||
|
||||
// InitServers initializes all MCP servers
|
||||
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 {
|
||||
if server.Config.IsDisabled() {
|
||||
continue
|
||||
@@ -299,27 +307,33 @@ func (h *MCPHost) GetEinoToolInfos(ctx context.Context) ([]*schema.ToolInfo, err
|
||||
var tools []*schema.ToolInfo
|
||||
for _, serverTools := range results {
|
||||
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
|
||||
}
|
||||
|
||||
var toolNames []string
|
||||
for _, tool := range serverTools.Tools {
|
||||
einoTool, err := h.GetEinoTool(ctx, serverTools.ServerName, tool.Name)
|
||||
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
|
||||
}
|
||||
einoToolInfo, err := einoTool.Info(ctx)
|
||||
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
|
||||
}
|
||||
einoToolInfo.Name = fmt.Sprintf("%s__%s", serverTools.ServerName, tool.Name)
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -13,16 +13,49 @@ import (
|
||||
"github.com/httprunner/httprunner/v5/uixt"
|
||||
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||
"github.com/httprunner/httprunner/v5/uixt/types"
|
||||
"github.com/mark3labs/mcp-go/client"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
"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.
|
||||
type MCPServer4XTDriver struct {
|
||||
mcpServer *server.MCPServer
|
||||
driverCache sync.Map // key is serial, value is *XTDriver
|
||||
tools []mcp.Tool // 本地维护的工具列表
|
||||
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.
|
||||
@@ -33,7 +66,8 @@ func NewMCPServer() *MCPServer4XTDriver {
|
||||
server.WithToolCapabilities(false),
|
||||
)
|
||||
s := &MCPServer4XTDriver{
|
||||
mcpServer: mcpServer,
|
||||
mcpServer: mcpServer,
|
||||
handlerMap: make(map[string]toolCall),
|
||||
}
|
||||
s.addTools()
|
||||
return s
|
||||
@@ -56,6 +90,7 @@ func (ums *MCPServer4XTDriver) addTools() {
|
||||
tapXYTool := mcp.NewTool("tap_xy", tapParams...)
|
||||
ums.mcpServer.AddTool(tapXYTool, ums.handleTapXY)
|
||||
ums.tools = append(ums.tools, tapXYTool)
|
||||
ums.handlerMap[tapXYTool.Name] = ums.handleTapXY
|
||||
log.Info().Str("name", tapXYTool.Name).Msg("Register tool")
|
||||
|
||||
// Swipe Tool
|
||||
@@ -67,6 +102,7 @@ func (ums *MCPServer4XTDriver) addTools() {
|
||||
swipeTool := mcp.NewTool("swipe", swipeParams...)
|
||||
ums.mcpServer.AddTool(swipeTool, ums.handleSwipe)
|
||||
ums.tools = append(ums.tools, swipeTool)
|
||||
ums.handlerMap[swipeTool.Name] = ums.handleSwipe
|
||||
log.Info().Str("name", swipeTool.Name).Msg("Register tool")
|
||||
|
||||
// ScreenShot Tool
|
||||
@@ -75,6 +111,7 @@ func (ums *MCPServer4XTDriver) addTools() {
|
||||
)
|
||||
ums.mcpServer.AddTool(screenShotTool, ums.handleScreenShot)
|
||||
ums.tools = append(ums.tools, screenShotTool)
|
||||
ums.handlerMap[screenShotTool.Name] = ums.handleScreenShot
|
||||
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")),
|
||||
}
|
||||
|
||||
// ListTools 返回所有注册的 mcp.Tool
|
||||
// ListTools returns all registered tools
|
||||
func (s *MCPServer4XTDriver) ListTools() []mcp.Tool {
|
||||
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 {
|
||||
for i := range s.tools {
|
||||
if s.tools[i].Name == name {
|
||||
@@ -273,3 +310,11 @@ func (s *MCPServer4XTDriver) GetTool(name string) *mcp.Tool {
|
||||
}
|
||||
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]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user