mirror of
https://github.com/httprunner/httprunner.git
synced 2026-09-05 07:26:55 +08:00
feat: hrp mcphost
This commit is contained in:
+2
-2
@@ -12,8 +12,8 @@ import (
|
|||||||
|
|
||||||
var CmdBuild = &cobra.Command{
|
var CmdBuild = &cobra.Command{
|
||||||
Use: "build $path ...",
|
Use: "build $path ...",
|
||||||
Short: "build plugin for testing",
|
Short: "Build plugin for testing",
|
||||||
Long: `build python/go plugin for testing`,
|
Long: `Build python/go plugin for testing`,
|
||||||
Example: ` $ hrp build plugin/debugtalk.go
|
Example: ` $ hrp build plugin/debugtalk.go
|
||||||
$ hrp build plugin/debugtalk.py`,
|
$ hrp build plugin/debugtalk.py`,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ func addAllCommands() {
|
|||||||
cmd.RootCmd.AddCommand(cmd.CmdScaffold)
|
cmd.RootCmd.AddCommand(cmd.CmdScaffold)
|
||||||
cmd.RootCmd.AddCommand(cmd.CmdServer)
|
cmd.RootCmd.AddCommand(cmd.CmdServer)
|
||||||
cmd.RootCmd.AddCommand(cmd.CmdWiki)
|
cmd.RootCmd.AddCommand(cmd.CmdWiki)
|
||||||
|
cmd.RootCmd.AddCommand(cmd.CmdMCPHost)
|
||||||
|
|
||||||
cmd.RootCmd.AddCommand(ios.CmdIOSRoot)
|
cmd.RootCmd.AddCommand(ios.CmdIOSRoot)
|
||||||
cmd.RootCmd.AddCommand(adb.CmdAndroidRoot)
|
cmd.RootCmd.AddCommand(adb.CmdAndroidRoot)
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ import (
|
|||||||
|
|
||||||
var CmdConvert = &cobra.Command{
|
var CmdConvert = &cobra.Command{
|
||||||
Use: "convert $path...",
|
Use: "convert $path...",
|
||||||
Short: "convert multiple source format to HttpRunner JSON/YAML/gotest/pytest cases",
|
Short: "Convert multiple source format to HttpRunner JSON/YAML/gotest/pytest cases",
|
||||||
Args: cobra.MinimumNArgs(1),
|
Args: cobra.MinimumNArgs(1),
|
||||||
SilenceUsage: false,
|
SilenceUsage: false,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/httprunner/httprunner/v5/pkg/mcphost"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdMCPHost represents the mcphost command
|
||||||
|
var CmdMCPHost = &cobra.Command{
|
||||||
|
Use: "mcphost",
|
||||||
|
Short: "Export MCP server tools to JSON description",
|
||||||
|
Long: `Export all tools from MCP servers to JSON description.
|
||||||
|
The tools will be exported with their descriptions, parameters, and return values.`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
// Create MCP host
|
||||||
|
host, err := mcphost.NewMCPHost(mcpConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create MCP host: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize servers
|
||||||
|
ctx := context.Background()
|
||||||
|
if err := host.InitServers(ctx); err != nil {
|
||||||
|
return fmt.Errorf("failed to initialize MCP servers: %w", err)
|
||||||
|
}
|
||||||
|
defer host.CloseServers()
|
||||||
|
|
||||||
|
// Export tools to JSON
|
||||||
|
if dumpPath != "" {
|
||||||
|
if err := host.ExportToolsToJSON(ctx, dumpPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
mcpConfigPath string
|
||||||
|
dumpPath string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
CmdMCPHost.Flags().StringVarP(&mcpConfigPath, "mcp-config", "c", "$HOME/.hrp/mcp.json", "path to the MCP config file")
|
||||||
|
CmdMCPHost.Flags().StringVar(&dumpPath, "dump", "", "path to save the exported tools JSON file")
|
||||||
|
}
|
||||||
+1
-1
@@ -15,7 +15,7 @@ import (
|
|||||||
|
|
||||||
var CmdPytest = &cobra.Command{
|
var CmdPytest = &cobra.Command{
|
||||||
Use: "pytest $path ...",
|
Use: "pytest $path ...",
|
||||||
Short: "run API test with pytest",
|
Short: "Run API test with pytest",
|
||||||
Args: cobra.MinimumNArgs(1),
|
Args: cobra.MinimumNArgs(1),
|
||||||
DisableFlagParsing: true, // allow to pass any args to pytest
|
DisableFlagParsing: true, // allow to pass any args to pytest
|
||||||
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||||
|
|||||||
+2
-2
@@ -9,8 +9,8 @@ import (
|
|||||||
// runCmd represents the run command
|
// runCmd represents the run command
|
||||||
var CmdRun = &cobra.Command{
|
var CmdRun = &cobra.Command{
|
||||||
Use: "run $path...",
|
Use: "run $path...",
|
||||||
Short: "run API test with go engine",
|
Short: "Run API test with go engine",
|
||||||
Long: `run yaml/json testcase files for API test`,
|
Long: `Run yaml/json testcase files for API test`,
|
||||||
Example: ` $ hrp run demo.json # run specified json testcase file
|
Example: ` $ hrp run demo.json # run specified json testcase file
|
||||||
$ hrp run demo.yaml # run specified yaml testcase file
|
$ hrp run demo.yaml # run specified yaml testcase file
|
||||||
$ hrp run examples/ # run testcases in specified folder`,
|
$ hrp run examples/ # run testcases in specified folder`,
|
||||||
|
|||||||
+2
-2
@@ -12,8 +12,8 @@ import (
|
|||||||
var CmdScaffold = &cobra.Command{
|
var CmdScaffold = &cobra.Command{
|
||||||
Use: "startproject $project_name",
|
Use: "startproject $project_name",
|
||||||
Aliases: []string{"scaffold"},
|
Aliases: []string{"scaffold"},
|
||||||
Short: "create a scaffold project",
|
Short: "Create a scaffold project",
|
||||||
Args: cobra.ExactValidArgs(1),
|
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
if !ignorePlugin && !genPythonPlugin && !genGoPlugin {
|
if !ignorePlugin && !genPythonPlugin && !genGoPlugin {
|
||||||
return errors.New("please specify function plugin type")
|
return errors.New("please specify function plugin type")
|
||||||
|
|||||||
+3
-6
@@ -10,8 +10,8 @@ import (
|
|||||||
// serverCmd represents the server command
|
// serverCmd represents the server command
|
||||||
var CmdServer = &cobra.Command{
|
var CmdServer = &cobra.Command{
|
||||||
Use: "server start",
|
Use: "server start",
|
||||||
Short: "start hrp server",
|
Short: "Start hrp server",
|
||||||
Long: `start hrp server, call httprunner by HTTP`,
|
Long: `Start hrp server, call httprunner by HTTP`,
|
||||||
Args: cobra.MinimumNArgs(1),
|
Args: cobra.MinimumNArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
router := server.NewRouter()
|
router := server.NewRouter()
|
||||||
@@ -23,10 +23,7 @@ var CmdServer = &cobra.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var port int
|
||||||
port int
|
|
||||||
mcpConfigPath string
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
CmdServer.Flags().IntVarP(&port, "port", "p", 8082, "port to run the server on")
|
CmdServer.Flags().IntVarP(&port, "port", "p", 8082, "port to run the server on")
|
||||||
|
|||||||
+8
-7
@@ -51,13 +51,14 @@ Copyright © 2017-present debugtalk. Apache-2.0 License.
|
|||||||
### SEE ALSO
|
### SEE ALSO
|
||||||
|
|
||||||
* [hrp adb](hrp_adb.md) - simple utils for android device management
|
* [hrp adb](hrp_adb.md) - simple utils for android device management
|
||||||
* [hrp build](hrp_build.md) - build plugin for testing
|
* [hrp build](hrp_build.md) - Build plugin for testing
|
||||||
* [hrp convert](hrp_convert.md) - convert multiple source format to HttpRunner JSON/YAML/gotest/pytest cases
|
* [hrp convert](hrp_convert.md) - Convert multiple source format to HttpRunner JSON/YAML/gotest/pytest cases
|
||||||
* [hrp ios](hrp_ios.md) - simple utils for ios device management
|
* [hrp ios](hrp_ios.md) - simple utils for ios device management
|
||||||
* [hrp pytest](hrp_pytest.md) - run API test with pytest
|
* [hrp mcphost](hrp_mcphost.md) - Export MCP server tools to JSON description
|
||||||
* [hrp run](hrp_run.md) - run API test with go engine
|
* [hrp pytest](hrp_pytest.md) - Run API test with pytest
|
||||||
* [hrp server](hrp_server.md) - start hrp server
|
* [hrp run](hrp_run.md) - Run API test with go engine
|
||||||
* [hrp startproject](hrp_startproject.md) - create a scaffold project
|
* [hrp server](hrp_server.md) - Start hrp server
|
||||||
|
* [hrp startproject](hrp_startproject.md) - Create a scaffold project
|
||||||
* [hrp wiki](hrp_wiki.md) - visit https://httprunner.com
|
* [hrp wiki](hrp_wiki.md) - visit https://httprunner.com
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 24-Apr-2025
|
###### Auto generated by spf13/cobra on 16-May-2025
|
||||||
|
|||||||
+1
-1
@@ -23,4 +23,4 @@ simple utils for android device management
|
|||||||
* [hrp adb install](hrp_adb_install.md) - push package to the device and install them automatically
|
* [hrp adb install](hrp_adb_install.md) - push package to the device and install them automatically
|
||||||
* [hrp adb screencap](hrp_adb_screencap.md) - Start android screen capture
|
* [hrp adb screencap](hrp_adb_screencap.md) - Start android screen capture
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 24-Apr-2025
|
###### Auto generated by spf13/cobra on 16-May-2025
|
||||||
|
|||||||
@@ -24,4 +24,4 @@ hrp adb devices [flags]
|
|||||||
|
|
||||||
* [hrp adb](hrp_adb.md) - simple utils for android device management
|
* [hrp adb](hrp_adb.md) - simple utils for android device management
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 24-Apr-2025
|
###### Auto generated by spf13/cobra on 16-May-2025
|
||||||
|
|||||||
@@ -28,4 +28,4 @@ hrp adb install [flags] PACKAGE
|
|||||||
|
|
||||||
* [hrp adb](hrp_adb.md) - simple utils for android device management
|
* [hrp adb](hrp_adb.md) - simple utils for android device management
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 24-Apr-2025
|
###### Auto generated by spf13/cobra on 16-May-2025
|
||||||
|
|||||||
@@ -25,4 +25,4 @@ hrp adb screencap [flags]
|
|||||||
|
|
||||||
* [hrp adb](hrp_adb.md) - simple utils for android device management
|
* [hrp adb](hrp_adb.md) - simple utils for android device management
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 24-Apr-2025
|
###### Auto generated by spf13/cobra on 16-May-2025
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
## hrp build
|
## hrp build
|
||||||
|
|
||||||
build plugin for testing
|
Build plugin for testing
|
||||||
|
|
||||||
### Synopsis
|
### Synopsis
|
||||||
|
|
||||||
build python/go plugin for testing
|
Build python/go plugin for testing
|
||||||
|
|
||||||
```
|
```
|
||||||
hrp build $path ... [flags]
|
hrp build $path ... [flags]
|
||||||
@@ -36,4 +36,4 @@ hrp build $path ... [flags]
|
|||||||
|
|
||||||
* [hrp](hrp.md) - All-in-One Testing Framework for API, UI and Performance
|
* [hrp](hrp.md) - All-in-One Testing Framework for API, UI and Performance
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 24-Apr-2025
|
###### Auto generated by spf13/cobra on 16-May-2025
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
## hrp convert
|
## hrp convert
|
||||||
|
|
||||||
convert multiple source format to HttpRunner JSON/YAML/gotest/pytest cases
|
Convert multiple source format to HttpRunner JSON/YAML/gotest/pytest cases
|
||||||
|
|
||||||
```
|
```
|
||||||
hrp convert $path... [flags]
|
hrp convert $path... [flags]
|
||||||
@@ -34,4 +34,4 @@ hrp convert $path... [flags]
|
|||||||
|
|
||||||
* [hrp](hrp.md) - All-in-One Testing Framework for API, UI and Performance
|
* [hrp](hrp.md) - All-in-One Testing Framework for API, UI and Performance
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 24-Apr-2025
|
###### Auto generated by spf13/cobra on 16-May-2025
|
||||||
|
|||||||
+1
-1
@@ -29,4 +29,4 @@ simple utils for ios device management
|
|||||||
* [hrp ios uninstall](hrp_ios_uninstall.md) - uninstall package automatically
|
* [hrp ios uninstall](hrp_ios_uninstall.md) - uninstall package automatically
|
||||||
* [hrp ios xctest](hrp_ios_xctest.md) - run xctest
|
* [hrp ios xctest](hrp_ios_xctest.md) - run xctest
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 24-Apr-2025
|
###### Auto generated by spf13/cobra on 16-May-2025
|
||||||
|
|||||||
@@ -26,4 +26,4 @@ hrp ios apps [flags]
|
|||||||
|
|
||||||
* [hrp ios](hrp_ios.md) - simple utils for ios device management
|
* [hrp ios](hrp_ios.md) - simple utils for ios device management
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 24-Apr-2025
|
###### Auto generated by spf13/cobra on 16-May-2025
|
||||||
|
|||||||
@@ -24,4 +24,4 @@ hrp ios devices [flags]
|
|||||||
|
|
||||||
* [hrp ios](hrp_ios.md) - simple utils for ios device management
|
* [hrp ios](hrp_ios.md) - simple utils for ios device management
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 24-Apr-2025
|
###### Auto generated by spf13/cobra on 16-May-2025
|
||||||
|
|||||||
@@ -25,4 +25,4 @@ hrp ios install [flags] PACKAGE
|
|||||||
|
|
||||||
* [hrp ios](hrp_ios.md) - simple utils for ios device management
|
* [hrp ios](hrp_ios.md) - simple utils for ios device management
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 24-Apr-2025
|
###### Auto generated by spf13/cobra on 16-May-2025
|
||||||
|
|||||||
@@ -28,4 +28,4 @@ hrp ios mount [flags]
|
|||||||
|
|
||||||
* [hrp ios](hrp_ios.md) - simple utils for ios device management
|
* [hrp ios](hrp_ios.md) - simple utils for ios device management
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 24-Apr-2025
|
###### Auto generated by spf13/cobra on 16-May-2025
|
||||||
|
|||||||
@@ -26,4 +26,4 @@ hrp ios ps [flags]
|
|||||||
|
|
||||||
* [hrp ios](hrp_ios.md) - simple utils for ios device management
|
* [hrp ios](hrp_ios.md) - simple utils for ios device management
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 24-Apr-2025
|
###### Auto generated by spf13/cobra on 16-May-2025
|
||||||
|
|||||||
@@ -25,4 +25,4 @@ hrp ios reboot [flags]
|
|||||||
|
|
||||||
* [hrp ios](hrp_ios.md) - simple utils for ios device management
|
* [hrp ios](hrp_ios.md) - simple utils for ios device management
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 24-Apr-2025
|
###### Auto generated by spf13/cobra on 16-May-2025
|
||||||
|
|||||||
@@ -24,4 +24,4 @@ hrp ios tunnel [flags]
|
|||||||
|
|
||||||
* [hrp ios](hrp_ios.md) - simple utils for ios device management
|
* [hrp ios](hrp_ios.md) - simple utils for ios device management
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 24-Apr-2025
|
###### Auto generated by spf13/cobra on 16-May-2025
|
||||||
|
|||||||
@@ -26,4 +26,4 @@ hrp ios uninstall [flags] PACKAGE
|
|||||||
|
|
||||||
* [hrp ios](hrp_ios.md) - simple utils for ios device management
|
* [hrp ios](hrp_ios.md) - simple utils for ios device management
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 24-Apr-2025
|
###### Auto generated by spf13/cobra on 16-May-2025
|
||||||
|
|||||||
@@ -28,4 +28,4 @@ hrp ios xctest [flags]
|
|||||||
|
|
||||||
* [hrp ios](hrp_ios.md) - simple utils for ios device management
|
* [hrp ios](hrp_ios.md) - simple utils for ios device management
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 24-Apr-2025
|
###### Auto generated by spf13/cobra on 16-May-2025
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
## hrp mcphost
|
||||||
|
|
||||||
|
Export MCP server tools to JSON description
|
||||||
|
|
||||||
|
### Synopsis
|
||||||
|
|
||||||
|
Export all tools from MCP servers to JSON description.
|
||||||
|
The tools will be exported with their descriptions, parameters, and return values.
|
||||||
|
|
||||||
|
```
|
||||||
|
hrp mcphost [flags]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
--dump string path to save the exported tools JSON file (default "tools_records.json")
|
||||||
|
-h, --help help for mcphost
|
||||||
|
-c, --mcp-config string path to the MCP config file (default "$HOME/.hrp/mcp.json")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options inherited from parent commands
|
||||||
|
|
||||||
|
```
|
||||||
|
--log-json set log to json format (default colorized console)
|
||||||
|
-l, --log-level string set log level (default "INFO")
|
||||||
|
--venv string specify python3 venv path
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEE ALSO
|
||||||
|
|
||||||
|
* [hrp](hrp.md) - All-in-One Testing Framework for API, UI and Performance
|
||||||
|
|
||||||
|
###### Auto generated by spf13/cobra on 16-May-2025
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
## hrp pytest
|
## hrp pytest
|
||||||
|
|
||||||
run API test with pytest
|
Run API test with pytest
|
||||||
|
|
||||||
```
|
```
|
||||||
hrp pytest $path ... [flags]
|
hrp pytest $path ... [flags]
|
||||||
@@ -24,4 +24,4 @@ hrp pytest $path ... [flags]
|
|||||||
|
|
||||||
* [hrp](hrp.md) - All-in-One Testing Framework for API, UI and Performance
|
* [hrp](hrp.md) - All-in-One Testing Framework for API, UI and Performance
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 24-Apr-2025
|
###### Auto generated by spf13/cobra on 16-May-2025
|
||||||
|
|||||||
+3
-3
@@ -1,10 +1,10 @@
|
|||||||
## hrp run
|
## hrp run
|
||||||
|
|
||||||
run API test with go engine
|
Run API test with go engine
|
||||||
|
|
||||||
### Synopsis
|
### Synopsis
|
||||||
|
|
||||||
run yaml/json testcase files for API test
|
Run yaml/json testcase files for API test
|
||||||
|
|
||||||
```
|
```
|
||||||
hrp run $path... [flags]
|
hrp run $path... [flags]
|
||||||
@@ -44,4 +44,4 @@ hrp run $path... [flags]
|
|||||||
|
|
||||||
* [hrp](hrp.md) - All-in-One Testing Framework for API, UI and Performance
|
* [hrp](hrp.md) - All-in-One Testing Framework for API, UI and Performance
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 24-Apr-2025
|
###### Auto generated by spf13/cobra on 16-May-2025
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
## hrp server
|
## hrp server
|
||||||
|
|
||||||
start hrp server
|
Start hrp server
|
||||||
|
|
||||||
### Synopsis
|
### Synopsis
|
||||||
|
|
||||||
start hrp server, call httprunner by HTTP
|
Start hrp server, call httprunner by HTTP
|
||||||
|
|
||||||
```
|
```
|
||||||
hrp server start [flags]
|
hrp server start [flags]
|
||||||
@@ -30,4 +30,4 @@ hrp server start [flags]
|
|||||||
|
|
||||||
* [hrp](hrp.md) - All-in-One Testing Framework for API, UI and Performance
|
* [hrp](hrp.md) - All-in-One Testing Framework for API, UI and Performance
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 24-Apr-2025
|
###### Auto generated by spf13/cobra on 16-May-2025
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
## hrp startproject
|
## hrp startproject
|
||||||
|
|
||||||
create a scaffold project
|
Create a scaffold project
|
||||||
|
|
||||||
```
|
```
|
||||||
hrp startproject $project_name [flags]
|
hrp startproject $project_name [flags]
|
||||||
@@ -29,4 +29,4 @@ hrp startproject $project_name [flags]
|
|||||||
|
|
||||||
* [hrp](hrp.md) - All-in-One Testing Framework for API, UI and Performance
|
* [hrp](hrp.md) - All-in-One Testing Framework for API, UI and Performance
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 24-Apr-2025
|
###### Auto generated by spf13/cobra on 16-May-2025
|
||||||
|
|||||||
@@ -24,4 +24,4 @@ hrp wiki [flags]
|
|||||||
|
|
||||||
* [hrp](hrp.md) - All-in-One Testing Framework for API, UI and Performance
|
* [hrp](hrp.md) - All-in-One Testing Framework for API, UI and Performance
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 24-Apr-2025
|
###### Auto generated by spf13/cobra on 16-May-2025
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
v5.0.0-beta-2505161143
|
v5.0.0-beta-2505161337
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ func LoadMCPConfig(configPath string) (*MCPConfig, error) {
|
|||||||
}
|
}
|
||||||
configPath = filepath.Join(homeDir, ".mcp.json")
|
configPath = filepath.Join(homeDir, ".mcp.json")
|
||||||
}
|
}
|
||||||
|
configPath = os.ExpandEnv(configPath)
|
||||||
|
|
||||||
// Check if config file exists
|
// Check if config file exists
|
||||||
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
||||||
|
|||||||
@@ -0,0 +1,185 @@
|
|||||||
|
package mcphost
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/bytedance/sonic"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MCPToolRecord represents a single tool record in the database
|
||||||
|
// Each record contains detailed information about a tool and its server
|
||||||
|
type MCPToolRecord struct {
|
||||||
|
ToolID string `json:"tool_id"` // Unique identifier for the tool record
|
||||||
|
ServerName string `json:"mcp_server"` // Name of the MCP server
|
||||||
|
ToolName string `json:"tool_name"` // Name of the tool
|
||||||
|
Description string `json:"description"` // Tool description
|
||||||
|
Parameters string `json:"parameters"` // Tool input parameters in JSON format
|
||||||
|
Returns string `json:"returns"` // Tool return value format in JSON format
|
||||||
|
CreatedAt time.Time `json:"created_at"` // Record creation time
|
||||||
|
LastUpdatedAt time.Time `json:"last_updated_at"` // Record last update time
|
||||||
|
}
|
||||||
|
|
||||||
|
// DocStringInfo contains the parsed information from a Python docstring
|
||||||
|
type DocStringInfo struct {
|
||||||
|
Description string
|
||||||
|
Parameters map[string]string
|
||||||
|
Returns map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractDocStringInfo extracts information from a Python docstring
|
||||||
|
// Example input:
|
||||||
|
// """Get weather alerts for a US state.
|
||||||
|
//
|
||||||
|
// Args:
|
||||||
|
// state: Two-letter US state code (e.g. CA, NY)
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// alerts: List of active weather alerts for the specified state
|
||||||
|
// error: Error message if the request fails
|
||||||
|
// """
|
||||||
|
func extractDocStringInfo(docstring string) DocStringInfo {
|
||||||
|
info := DocStringInfo{
|
||||||
|
Parameters: make(map[string]string),
|
||||||
|
Returns: make(map[string]string),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the Args and Returns sections
|
||||||
|
argsIndex := strings.Index(docstring, "Args:")
|
||||||
|
returnsIndex := strings.Index(docstring, "Returns:")
|
||||||
|
|
||||||
|
// Extract description (everything before Args)
|
||||||
|
if argsIndex != -1 {
|
||||||
|
info.Description = strings.TrimSpace(docstring[:argsIndex])
|
||||||
|
} else if returnsIndex != -1 {
|
||||||
|
info.Description = strings.TrimSpace(docstring[:returnsIndex])
|
||||||
|
} else {
|
||||||
|
info.Description = strings.TrimSpace(docstring)
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to extract key-value pairs from a section
|
||||||
|
extractSection := func(content string) map[string]string {
|
||||||
|
result := make(map[string]string)
|
||||||
|
lines := strings.Split(content, "\n")
|
||||||
|
|
||||||
|
for _, line := range lines {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.SplitN(line, ":", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
key := strings.TrimSpace(parts[0])
|
||||||
|
value := strings.TrimSpace(parts[1])
|
||||||
|
|
||||||
|
if key != "" && value != "" {
|
||||||
|
result[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract Args section
|
||||||
|
if argsIndex != -1 {
|
||||||
|
endIndex := returnsIndex
|
||||||
|
if endIndex == -1 {
|
||||||
|
endIndex = len(docstring)
|
||||||
|
}
|
||||||
|
argsContent := docstring[argsIndex+len("Args:") : endIndex]
|
||||||
|
info.Parameters = extractSection(argsContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract Returns section
|
||||||
|
if returnsIndex != -1 {
|
||||||
|
returnsContent := docstring[returnsIndex+len("Returns:"):]
|
||||||
|
info.Returns = extractSection(returnsContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertToolsToRecords converts map[string]MCPTools to a list of database records
|
||||||
|
func ConvertToolsToRecords(toolsMap map[string]MCPTools) []MCPToolRecord {
|
||||||
|
var records []MCPToolRecord
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
for serverName, mcpTools := range toolsMap {
|
||||||
|
if mcpTools.Err != nil {
|
||||||
|
log.Error().Str("server", serverName).Err(mcpTools.Err).Msg("skip tools conversion due to error")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tool := range mcpTools.Tools {
|
||||||
|
// Generate unique ID by combining server name and tool name
|
||||||
|
id := fmt.Sprintf("%s_%s", serverName, tool.Name)
|
||||||
|
|
||||||
|
// Extract docstring information
|
||||||
|
info := extractDocStringInfo(tool.Description)
|
||||||
|
|
||||||
|
// Convert parameters and returns to JSON
|
||||||
|
paramsJSON, err := sonic.MarshalString(info.Parameters)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn().Interface("params", info.Parameters).Err(err).Msg("failed to marshal parameters to JSON")
|
||||||
|
paramsJSON = "{}"
|
||||||
|
}
|
||||||
|
|
||||||
|
returnsJSON, err := sonic.MarshalString(info.Returns)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn().Interface("returns", info.Returns).Err(err).Msg("failed to marshal returns to JSON")
|
||||||
|
returnsJSON = "{}"
|
||||||
|
}
|
||||||
|
|
||||||
|
record := MCPToolRecord{
|
||||||
|
ToolID: id,
|
||||||
|
ServerName: serverName,
|
||||||
|
ToolName: tool.Name,
|
||||||
|
Description: info.Description,
|
||||||
|
Parameters: paramsJSON,
|
||||||
|
Returns: returnsJSON,
|
||||||
|
CreatedAt: now,
|
||||||
|
LastUpdatedAt: now,
|
||||||
|
}
|
||||||
|
|
||||||
|
records = append(records, record)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return records
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportToolsToJSON dumps MCP tools to JSON file
|
||||||
|
func (h *MCPHost) ExportToolsToJSON(ctx context.Context, dumpPath string) error {
|
||||||
|
// get all tools
|
||||||
|
tools := h.GetTools(ctx)
|
||||||
|
// convert to records
|
||||||
|
records := ConvertToolsToRecords(tools)
|
||||||
|
// convert to JSON
|
||||||
|
recordsJSON, err := sonic.MarshalIndent(records, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to marshal records to JSON: %w", err)
|
||||||
|
}
|
||||||
|
// create output directory
|
||||||
|
outputDir := filepath.Dir(dumpPath)
|
||||||
|
if outputDir != "." {
|
||||||
|
if err := os.MkdirAll(outputDir, 0o755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create output directory: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// write to file
|
||||||
|
if err := os.WriteFile(dumpPath, []byte(recordsJSON), 0o644); err != nil {
|
||||||
|
return fmt.Errorf("failed to write records to file: %w", err)
|
||||||
|
}
|
||||||
|
log.Info().Str("path", dumpPath).Msg("Tools records exported successfully")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
package mcphost
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mark3labs/mcp-go/mcp"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConvertToolsToRecordsFromFile(t *testing.T) {
|
||||||
|
hub, err := NewMCPHost("./testdata/test.mcp.json")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
err = hub.InitServers(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// use ExportToolsToJSON to dump tools to JSON file
|
||||||
|
err = hub.ExportToolsToJSON(ctx, "./tools_records.json")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// read the exported JSON file
|
||||||
|
data, err := os.ReadFile("./tools_records.json")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// parse the exported JSON data
|
||||||
|
var records []MCPToolRecord
|
||||||
|
err = json.Unmarshal(data, &records)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// verify the number of records
|
||||||
|
assert.NotEmpty(t, records, "Exported records should not be empty")
|
||||||
|
|
||||||
|
t.Logf("Tools records written to ./tools_records.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExtractDocStringInfo(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
docstring string
|
||||||
|
want DocStringInfo
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "complete docstring with args and returns",
|
||||||
|
docstring: `Get weather alerts for a US state.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
state: Two-letter US state code (e.g. CA, NY)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
alerts: List of active weather alerts for the specified state
|
||||||
|
error: Error message if the request fails
|
||||||
|
`,
|
||||||
|
want: DocStringInfo{
|
||||||
|
Description: "Get weather alerts for a US state.",
|
||||||
|
Parameters: map[string]string{
|
||||||
|
"state": "Two-letter US state code (e.g. CA, NY)",
|
||||||
|
},
|
||||||
|
Returns: map[string]string{
|
||||||
|
"alerts": "List of active weather alerts for the specified state",
|
||||||
|
"error": "Error message if the request fails",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "docstring with only args",
|
||||||
|
docstring: `Do screen swipe action.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
direction: swipe direction (up, down)
|
||||||
|
`,
|
||||||
|
want: DocStringInfo{
|
||||||
|
Description: "Do screen swipe action.",
|
||||||
|
Parameters: map[string]string{
|
||||||
|
"direction": "swipe direction (up, down)",
|
||||||
|
},
|
||||||
|
Returns: map[string]string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "docstring with only description",
|
||||||
|
docstring: "Simple tool with no parameters.",
|
||||||
|
want: DocStringInfo{
|
||||||
|
Description: "Simple tool with no parameters.",
|
||||||
|
Parameters: map[string]string{},
|
||||||
|
Returns: map[string]string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "docstring with multiple parameters",
|
||||||
|
docstring: `Perform complex operation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
param1: first parameter description
|
||||||
|
param2: second parameter description
|
||||||
|
param3: third parameter description
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
result: operation result
|
||||||
|
`,
|
||||||
|
want: DocStringInfo{
|
||||||
|
Description: "Perform complex operation.",
|
||||||
|
Parameters: map[string]string{
|
||||||
|
"param1": "first parameter description",
|
||||||
|
"param2": "second parameter description",
|
||||||
|
"param3": "third parameter description",
|
||||||
|
},
|
||||||
|
Returns: map[string]string{
|
||||||
|
"result": "operation result",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := extractDocStringInfo(tt.docstring)
|
||||||
|
assert.Equal(t, tt.want.Description, got.Description)
|
||||||
|
assert.Equal(t, tt.want.Parameters, got.Parameters)
|
||||||
|
assert.Equal(t, tt.want.Returns, got.Returns)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvertToolsToRecords(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
toolsMap map[string]MCPTools
|
||||||
|
want []MCPToolRecord
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "convert weather tool",
|
||||||
|
toolsMap: map[string]MCPTools{
|
||||||
|
"weather": {
|
||||||
|
Name: "weather",
|
||||||
|
Tools: []mcp.Tool{
|
||||||
|
{
|
||||||
|
Name: "get_alerts",
|
||||||
|
Description: `Get weather alerts for a US state.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
state: Two-letter US state code (e.g. CA, NY)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
alerts: List of active weather alerts for the specified state
|
||||||
|
error: Error message if the request fails
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: []MCPToolRecord{
|
||||||
|
{
|
||||||
|
ToolID: "weather_get_alerts",
|
||||||
|
ServerName: "weather",
|
||||||
|
ToolName: "get_alerts",
|
||||||
|
Description: "Get weather alerts for a US state.",
|
||||||
|
Parameters: `{"state":"Two-letter US state code (e.g. CA, NY)"}`,
|
||||||
|
Returns: `{"alerts":"List of active weather alerts for the specified state","error":"Error message if the request fails"}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "convert multiple tools",
|
||||||
|
toolsMap: map[string]MCPTools{
|
||||||
|
"ui": {
|
||||||
|
Name: "ui",
|
||||||
|
Tools: []mcp.Tool{
|
||||||
|
{
|
||||||
|
Name: "swipe",
|
||||||
|
Description: `Do screen swipe action.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
direction: swipe direction (up, down)
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "tap",
|
||||||
|
Description: "Tap on screen at specified position.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: []MCPToolRecord{
|
||||||
|
{
|
||||||
|
ToolID: "ui_swipe",
|
||||||
|
ServerName: "ui",
|
||||||
|
ToolName: "swipe",
|
||||||
|
Description: "Do screen swipe action.",
|
||||||
|
Parameters: `{"direction":"swipe direction (up, down)"}`,
|
||||||
|
Returns: "{}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ToolID: "ui_tap",
|
||||||
|
ServerName: "ui",
|
||||||
|
ToolName: "tap",
|
||||||
|
Description: "Tap on screen at specified position.",
|
||||||
|
Parameters: "{}",
|
||||||
|
Returns: "{}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := ConvertToolsToRecords(tt.toolsMap)
|
||||||
|
|
||||||
|
// Compare each record
|
||||||
|
require.Equal(t, len(tt.want), len(got))
|
||||||
|
for i := range tt.want {
|
||||||
|
assert.Equal(t, tt.want[i].ToolID, got[i].ToolID)
|
||||||
|
assert.Equal(t, tt.want[i].ServerName, got[i].ServerName)
|
||||||
|
assert.Equal(t, tt.want[i].ToolName, got[i].ToolName)
|
||||||
|
assert.Equal(t, tt.want[i].Description, got[i].Description)
|
||||||
|
|
||||||
|
// Compare JSON content (ignoring whitespace differences)
|
||||||
|
var wantParams, gotParams, wantReturns, gotReturns map[string]string
|
||||||
|
require.NoError(t, json.Unmarshal([]byte(tt.want[i].Parameters), &wantParams))
|
||||||
|
require.NoError(t, json.Unmarshal([]byte(got[i].Parameters), &gotParams))
|
||||||
|
require.NoError(t, json.Unmarshal([]byte(tt.want[i].Returns), &wantReturns))
|
||||||
|
require.NoError(t, json.Unmarshal([]byte(got[i].Returns), &gotReturns))
|
||||||
|
|
||||||
|
assert.Equal(t, wantParams, gotParams)
|
||||||
|
assert.Equal(t, wantReturns, gotReturns)
|
||||||
|
|
||||||
|
// Verify timestamps are recent (within last 5 seconds)
|
||||||
|
now := time.Now()
|
||||||
|
assert.True(t, now.Sub(got[i].CreatedAt) < 5*time.Second, "CreatedAt should be recent")
|
||||||
|
assert.True(t, now.Sub(got[i].LastUpdatedAt) < 5*time.Second, "LastUpdatedAt should be recent")
|
||||||
|
// CreatedAt and LastUpdatedAt should be the same
|
||||||
|
assert.Equal(t, got[i].CreatedAt, got[i].LastUpdatedAt)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -169,9 +169,9 @@ func TestConcurrentOperations(t *testing.T) {
|
|||||||
|
|
||||||
// Test concurrent tool invocations
|
// Test concurrent tool invocations
|
||||||
done := make(chan bool)
|
done := make(chan bool)
|
||||||
timeout := time.After(10 * time.Second) // Increase timeout to 10 seconds
|
timeout := time.After(30 * time.Second) // Increase timeout to 30 seconds
|
||||||
|
|
||||||
for i := 0; i < 5; i++ {
|
for i := 0; i < 3; i++ { // Reduce number of concurrent operations to 3
|
||||||
go func() {
|
go func() {
|
||||||
result, err := host.InvokeTool(ctx, "weather", "get_alerts",
|
result, err := host.InvokeTool(ctx, "weather", "get_alerts",
|
||||||
map[string]interface{}{"state": "CA"},
|
map[string]interface{}{"state": "CA"},
|
||||||
@@ -183,7 +183,7 @@ func TestConcurrentOperations(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wait for all goroutines to complete
|
// Wait for all goroutines to complete
|
||||||
for i := 0; i < 5; i++ {
|
for i := 0; i < 3; i++ { // Update loop count to match the number of goroutines
|
||||||
select {
|
select {
|
||||||
case <-done:
|
case <-done:
|
||||||
// Success
|
// Success
|
||||||
|
|||||||
Reference in New Issue
Block a user