mirror of
https://github.com/httprunner/httprunner.git
synced 2026-09-05 23:51:25 +08:00
feat: call CallMCPTool in parser
This commit is contained in:
@@ -1 +1 @@
|
|||||||
v5.0.0-beta-2505161414
|
v5.0.0-beta-2505161430
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package hrp
|
package hrp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
builtinJSON "encoding/json"
|
builtinJSON "encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -18,14 +19,20 @@ import (
|
|||||||
"github.com/httprunner/funplugin/fungo"
|
"github.com/httprunner/funplugin/fungo"
|
||||||
"github.com/httprunner/httprunner/v5/code"
|
"github.com/httprunner/httprunner/v5/code"
|
||||||
"github.com/httprunner/httprunner/v5/internal/builtin"
|
"github.com/httprunner/httprunner/v5/internal/builtin"
|
||||||
|
"github.com/httprunner/httprunner/v5/pkg/mcphost"
|
||||||
|
mcp2 "github.com/mark3labs/mcp-go/mcp"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewParser() *Parser {
|
func NewParser() *Parser {
|
||||||
return &Parser{}
|
return &Parser{
|
||||||
|
ctx: context.Background(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Parser struct {
|
type Parser struct {
|
||||||
Plugin funplugin.IPlugin // plugin is used to call functions
|
ctx context.Context
|
||||||
|
Plugin funplugin.IPlugin // plugin is used to call functions
|
||||||
|
MCPHost *mcphost.MCPHost
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildURL(baseURL, stepURL string, queryParams url.Values) (fullUrl *url.URL) {
|
func buildURL(baseURL, stepURL string, queryParams url.Values) (fullUrl *url.URL) {
|
||||||
@@ -213,7 +220,7 @@ func (p *Parser) ParseString(raw string, variablesMapping map[string]interface{}
|
|||||||
return raw, err
|
return raw, err
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := p.callFunc(funcName, parsedArgs.([]interface{})...)
|
result, err := p.CallFunc(funcName, parsedArgs.([]interface{})...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Str("funcName", funcName).Interface("arguments", arguments).
|
log.Error().Str("funcName", funcName).Interface("arguments", arguments).
|
||||||
Err(err).Msg("call function failed")
|
Err(err).Msg("call function failed")
|
||||||
@@ -275,9 +282,9 @@ func (p *Parser) ParseString(raw string, variablesMapping map[string]interface{}
|
|||||||
return parsedString, nil
|
return parsedString, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// callFunc calls function with arguments
|
// CallFunc calls function with arguments
|
||||||
// only support return at most one result value
|
// only support return at most one result value
|
||||||
func (p *Parser) callFunc(funcName string, arguments ...interface{}) (interface{}, error) {
|
func (p *Parser) CallFunc(funcName string, arguments ...interface{}) (interface{}, error) {
|
||||||
// call with plugin function
|
// call with plugin function
|
||||||
if p.Plugin != nil {
|
if p.Plugin != nil {
|
||||||
if p.Plugin.Has(funcName) {
|
if p.Plugin.Has(funcName) {
|
||||||
@@ -300,6 +307,34 @@ func (p *Parser) callFunc(funcName string, arguments ...interface{}) (interface{
|
|||||||
return fungo.CallFunc(fn, arguments...)
|
return fungo.CallFunc(fn, arguments...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CallMCPTool calls a MCP tool on a specific MCP server
|
||||||
|
func (p *Parser) CallMCPTool(serverName, funcName string, arguments map[string]interface{}) (interface{}, error) {
|
||||||
|
if p.MCPHost == nil {
|
||||||
|
return nil, fmt.Errorf("mcphost is not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
tools := p.MCPHost.GetTools(p.ctx)
|
||||||
|
log.Warn().Interface("tools", tools).Msg("tools")
|
||||||
|
|
||||||
|
result, err := p.MCPHost.InvokeTool(p.ctx, serverName, funcName, arguments)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "invoke tool %s/%s failed", serverName, funcName)
|
||||||
|
}
|
||||||
|
if result.IsError {
|
||||||
|
return nil, fmt.Errorf("invoke tool %s/%s failed: %v", serverName, funcName, result.Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract text content
|
||||||
|
var resultText string
|
||||||
|
for _, item := range result.Content {
|
||||||
|
if contentMap, ok := item.(mcp2.TextContent); ok {
|
||||||
|
resultText += fmt.Sprintf("%v ", contentMap.Text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultText, nil
|
||||||
|
}
|
||||||
|
|
||||||
// merge two variables mapping, the first variables have higher priority
|
// merge two variables mapping, the first variables have higher priority
|
||||||
func mergeVariables(variables, overriddenVariables map[string]interface{}) map[string]interface{} {
|
func mergeVariables(variables, overriddenVariables map[string]interface{}) map[string]interface{} {
|
||||||
if overriddenVariables == nil {
|
if overriddenVariables == nil {
|
||||||
|
|||||||
+23
-4
@@ -9,6 +9,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/httprunner/httprunner/v5/internal/version"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -433,26 +434,44 @@ func TestCallBuiltinFunction(t *testing.T) {
|
|||||||
parser := NewParser()
|
parser := NewParser()
|
||||||
|
|
||||||
// call function without arguments
|
// call function without arguments
|
||||||
_, err := parser.callFunc("get_timestamp")
|
_, err := parser.CallFunc("get_timestamp")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
// call function with one argument
|
// call function with one argument
|
||||||
timeStart := time.Now()
|
timeStart := time.Now()
|
||||||
_, err = parser.callFunc("sleep", 1)
|
_, err = parser.CallFunc("sleep", 1)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Greater(t, time.Since(timeStart), time.Duration(1)*time.Second)
|
assert.Greater(t, time.Since(timeStart), time.Duration(1)*time.Second)
|
||||||
|
|
||||||
// call function with one argument
|
// call function with one argument
|
||||||
result, err := parser.callFunc("gen_random_string", 10)
|
result, err := parser.CallFunc("gen_random_string", 10)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 10, len(result.(string)))
|
assert.Equal(t, 10, len(result.(string)))
|
||||||
|
|
||||||
// call function with two argument
|
// call function with two argument
|
||||||
result, err = parser.callFunc("max", float64(10), 9.99)
|
result, err = parser.CallFunc("max", float64(10), 9.99)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, float64(10), result.(float64))
|
assert.Equal(t, float64(10), result.(float64))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCallMCPTool(t *testing.T) {
|
||||||
|
// Create a new case runner for testing
|
||||||
|
caseRunner, err := NewCaseRunner(TestCase{
|
||||||
|
Config: &TConfig{
|
||||||
|
MCPConfigPath: "pkg/mcphost/testdata/test.mcp.json",
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
parser := caseRunner.GetParser()
|
||||||
|
|
||||||
|
resp, err := parser.CallMCPTool("filesystem", "read_file",
|
||||||
|
map[string]interface{}{"path": "internal/version/VERSION"})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
t.Logf("resp: %v", resp)
|
||||||
|
assert.Contains(t, resp, version.VERSION)
|
||||||
|
}
|
||||||
|
|
||||||
func TestLiteralEval(t *testing.T) {
|
func TestLiteralEval(t *testing.T) {
|
||||||
testData := []struct {
|
testData := []struct {
|
||||||
expr string
|
expr string
|
||||||
|
|||||||
Reference in New Issue
Block a user