From 3e0a5f02a7ead8a71735a390240325a5578a28c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Thu, 28 Aug 2025 19:28:37 +0800 Subject: [PATCH] feat(wasm-plugin): add jsonrpc-converter plugin (#2805) --- .../extensions/jsonrpc-converter/go.mod | 35 +++ .../extensions/jsonrpc-converter/go.sum | 75 +++++ .../extensions/jsonrpc-converter/main.go | 271 ++++++++++++++++++ .../extensions/jsonrpc-converter/main_test.go | 28 ++ plugins/wasm-go/mcp-filters/mcp-router/go.mod | 2 +- plugins/wasm-go/mcp-filters/mcp-router/go.sum | 2 + .../wasm-go/mcp-filters/mcp-router/main.go | 2 +- plugins/wasm-go/mcp-servers/all-in-one/go.mod | 2 +- plugins/wasm-go/mcp-servers/all-in-one/go.sum | 2 + 9 files changed, 416 insertions(+), 3 deletions(-) create mode 100644 plugins/wasm-go/extensions/jsonrpc-converter/go.mod create mode 100644 plugins/wasm-go/extensions/jsonrpc-converter/go.sum create mode 100644 plugins/wasm-go/extensions/jsonrpc-converter/main.go create mode 100644 plugins/wasm-go/extensions/jsonrpc-converter/main_test.go diff --git a/plugins/wasm-go/extensions/jsonrpc-converter/go.mod b/plugins/wasm-go/extensions/jsonrpc-converter/go.mod new file mode 100644 index 000000000..bf236a587 --- /dev/null +++ b/plugins/wasm-go/extensions/jsonrpc-converter/go.mod @@ -0,0 +1,35 @@ +module jsonrpc-converter + +go 1.24.3 + +require ( + github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250611100342-5654e89a7a80 + github.com/higress-group/wasm-go v1.0.2-0.20250819092116-2fd2b083a8e2 + github.com/tidwall/gjson v1.18.0 +) + +require ( + dario.cat/mergo v1.0.1 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.3.0 // indirect + github.com/Masterminds/sprig/v3 v3.3.0 // indirect + github.com/bahlo/generic-list-go v0.2.0 // indirect + github.com/buger/jsonparser v1.1.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/higress-group/gjson_template v0.0.0-20250413075336-4c4161ed428b // indirect + github.com/huandu/xstrings v1.5.0 // indirect + github.com/invopop/jsonschema v0.13.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/shopspring/decimal v1.4.0 // indirect + github.com/spf13/cast v1.7.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/resp v0.1.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect + golang.org/x/crypto v0.26.0 // indirect + google.golang.org/protobuf v1.36.6 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/plugins/wasm-go/extensions/jsonrpc-converter/go.sum b/plugins/wasm-go/extensions/jsonrpc-converter/go.sum new file mode 100644 index 000000000..1fb94f087 --- /dev/null +++ b/plugins/wasm-go/extensions/jsonrpc-converter/go.sum @@ -0,0 +1,75 @@ +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= +github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= +github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= +github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/higress-group/gjson_template v0.0.0-20250413075336-4c4161ed428b h1:rRI9+ThQbe+nw4jUiYEyOFaREkXCMMW9k1X2gy2d6pE= +github.com/higress-group/gjson_template v0.0.0-20250413075336-4c4161ed428b/go.mod h1:rU3M+Tq5VrQOo0dxpKHGb03Ty0sdWIZfAH+YCOACx/Y= +github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250611100342-5654e89a7a80 h1:xqmtTZI0JQ2O+Lg9/CE6c+Tw9KD6FnvWw8EpLVuuvfg= +github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250611100342-5654e89a7a80/go.mod h1:tRI2LfMudSkKHhyv1uex3BWzcice2s/l8Ah8axporfA= +github.com/higress-group/wasm-go v1.0.2-0.20250724062140-49cd26dcf15a h1:WCnqxeHgvV5LZvPx37EBWi1vXQ3Aw5ldFezZlG4l5Zk= +github.com/higress-group/wasm-go v1.0.2-0.20250724062140-49cd26dcf15a/go.mod h1:9k7L730huS/q4V5iH9WLDgf5ZUHEtfhM/uXcegKDG/M= +github.com/higress-group/wasm-go v1.0.2-0.20250819092116-2fd2b083a8e2 h1:2wlbNpFJCQNbPBFYgswz7Zvxo9O3L0PH0AJxwiCc5lk= +github.com/higress-group/wasm-go v1.0.2-0.20250819092116-2fd2b083a8e2/go.mod h1:9k7L730huS/q4V5iH9WLDgf5ZUHEtfhM/uXcegKDG/M= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E= +github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/resp v0.1.1 h1:Ly20wkhqKTmDUPlyM1S7pWo5kk0tDu8OoC/vFArXmwE= +github.com/tidwall/resp v0.1.1/go.mod h1:3/FrruOBAxPTPtundW0VXgmsQ4ZBA0Aw714lVYgwFa0= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= +github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/plugins/wasm-go/extensions/jsonrpc-converter/main.go b/plugins/wasm-go/extensions/jsonrpc-converter/main.go new file mode 100644 index 000000000..cfe58bf73 --- /dev/null +++ b/plugins/wasm-go/extensions/jsonrpc-converter/main.go @@ -0,0 +1,271 @@ +package main + +import ( + "encoding/json" + "fmt" + "slices" + "strconv" + + "github.com/higress-group/proxy-wasm-go-sdk/proxywasm" + "github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types" + "github.com/higress-group/wasm-go/pkg/log" + "github.com/higress-group/wasm-go/pkg/mcp" + "github.com/higress-group/wasm-go/pkg/mcp/utils" + "github.com/higress-group/wasm-go/pkg/wrapper" + "github.com/tidwall/gjson" +) + +func main() {} + +const ( + JsonRpcId = "x-envoy-jsonrpc-id" + JsonRpcMethod = "x-envoy-jsonrpc-method" + JsonRpcParams = "x-envoy-jsonrpc-params" + JsonRpcResult = "x-envoy-jsonrpc-result" + JsonRpcError = "x-envoy-jsonrpc-error" + McpToolName = "x-envoy-mcp-tool-name" + McpToolArguments = "x-envoy-mcp-tool-arguments" + McpToolResponse = "x-envoy-mcp-tool-response" + McpToolError = "x-envoy-mcp-tool-error" + + DefaultMaxHeaderLength = 4000 // default max length for truncation + MethodToolList = "tools/list" // default method for tool list + MethodToolCall = "tools/call" // default method for tool call +) + +type ProcessStage string + +const ( + ProcessRequest ProcessStage = "request" + ProcessResponse ProcessStage = "response" +) + +type McpConverterConfig struct { + Stage ProcessStage `json:"stage"` + MaxHeaderLength int `json:"max_header_length,omitempty"` + AllowedMethods []string `json:"allowed_methods,omitempty"` // optional, for future use +} + +func init() { + mcp.LoadMCPFilter( + mcp.FilterName("jsonrpc-converter"), + mcp.SetConfigParser(parseConfig), + mcp.SetJsonRpcRequestFilter(processJsonRpcRequest), + mcp.SetJsonRpcResponseFilter(processJsonRpcResponse), + mcp.SetToolListResponseFilter(processToolListResponse), + mcp.SetToolCallRequestFilter(processToolCallRequest), + mcp.SetToolCallResponseFilter(processToolCallResponse), + ) + mcp.InitMCPFilter() +} + +func parseConfig(configBytes []byte, filterConfig *any) error { + var config McpConverterConfig + if err := json.Unmarshal(configBytes, &config); err != nil { + return fmt.Errorf("failed to parse mcp-converter config: %v", err) + } + // validate stage + if config.Stage != ProcessRequest && config.Stage != ProcessResponse { + return fmt.Errorf("invalid mcp-converter stage: %s, must be 'request' or 'response'", config.Stage) + } + // validate length + if config.MaxHeaderLength <= 0 { + config.MaxHeaderLength = DefaultMaxHeaderLength + } + // validate allowed methods + if len(config.AllowedMethods) == 0 { + config.AllowedMethods = []string{MethodToolList, MethodToolCall} + } + log.Infof("MCP Converter config parsed successfully, stage: %s", config.Stage) + *filterConfig = config + return nil +} + +func isPreRequestStage(config any) bool { + return config.(McpConverterConfig).Stage == ProcessRequest +} + +func isPreResponseStage(config any) bool { + return config.(McpConverterConfig).Stage == ProcessResponse +} + +func isMethodAllowed(config any, method string) bool { + allowedMethods := config.(McpConverterConfig).AllowedMethods + return slices.Contains(allowedMethods, method) +} + +// Remove jsonrpc headers +func removeJsonRpcHeaders(isRequest bool) { + headersToRemove := []string{ + JsonRpcId, + JsonRpcMethod, + JsonRpcParams, + JsonRpcResult, + McpToolName, + McpToolArguments, + McpToolResponse, + McpToolError, + } + for _, header := range headersToRemove { + var err error + if isRequest { + err = proxywasm.RemoveHttpRequestHeader(header) + } else { + err = proxywasm.RemoveHttpResponseHeader(header) + } + if err != nil { + log.Errorf("failed to remove header %s: %v", header, err) + } + } +} + +// Insert jsonrpc headers +func insertJsonRpcHeaders(isRequest bool, config any, name string, value string) { + if value == "" { + log.Debugf("Skipping insertion of empty header %s", name) + return + } + truncatedValue := truncateString(value, config) + var err error + if isRequest { + err = proxywasm.ReplaceHttpRequestHeader(name, truncatedValue) + } else { + err = proxywasm.ReplaceHttpResponseHeader(name, truncatedValue) + } + if err != nil { + log.Errorf("failed to insert header %s: %v", name, err) + } +} + +func printHeaders(stage ProcessStage, s string) { + var err error + var headersNow any + switch stage { + case ProcessRequest: + headersNow, err = proxywasm.GetHttpRequestHeaders() + case ProcessResponse: + headersNow, err = proxywasm.GetHttpResponseHeaders() + } + if err != nil { + log.Errorf("PrintHeaders %s: failed to get request headers: %v", s, err) + return + } + log.Debugf("PrintHeaders %s: %v", s, headersNow) +} + +// truncates a string to a maximum length of 4000 characters. +func truncateString(s string, config any) string { + length := config.(McpConverterConfig).MaxHeaderLength + if len(s) <= length { + return s + } + prefix := s[:length/2] + suffix := s[len(s)-length/2:] + + return fmt.Sprintf("%s...(truncated)...%s", prefix, suffix) +} + +func processJsonRpcRequest(context wrapper.HttpContext, config any, id utils.JsonRpcID, method string, params gjson.Result, rawBody []byte) types.Action { + if isPreResponseStage(config) { + // pre-response removes request headers, which are added by pre-request + removeJsonRpcHeaders(true) + return types.ActionContinue + } + + if !isMethodAllowed(config, method) { + log.Debugf("[JsonRpcRequest] Method %s is not allowed, skipping processing", method) + return types.ActionContinue + } + + // Set common headers, JsonRpcId, JsonRpcMethod + insertJsonRpcHeaders(true, config, JsonRpcId, id.StringValue) + insertJsonRpcHeaders(true, config, JsonRpcMethod, method) + + // Set other headers based on the method + // For MethodToolCall, we set the params in processToolCallRequest + if method != MethodToolCall { + // JsonRpcParams + insertJsonRpcHeaders(true, config, JsonRpcParams, params.String()) + } + + return types.ActionContinue +} + +func processJsonRpcResponse(context wrapper.HttpContext, config any, id utils.JsonRpcID, result, error gjson.Result, rawBody []byte) types.Action { + if isPreRequestStage(config) { + // pre-request removes response headers, which are added by pre-response + removeJsonRpcHeaders(false) + return types.ActionContinue + } + + method := context.GetStringContext("JSONRPC_METHOD", "") + if !isMethodAllowed(config, method) { + log.Debugf("[JsonRpcResponse] Method %s is not allowed, skipping processing", method) + return types.ActionContinue + } + + // Set common headers, JsonRpcId, JsonRpcMethod + insertJsonRpcHeaders(false, config, JsonRpcId, id.StringValue) + insertJsonRpcHeaders(false, config, JsonRpcMethod, method) + + // Set other headers based on the method + // For MethodToolList & MethodToolCall, we set the params in processToolCallResponse and processToolListResponse + if method != MethodToolList && method != MethodToolCall { + // JsonRpcResult + insertJsonRpcHeaders(false, config, JsonRpcResult, result.String()) + // JsonRpcError + insertJsonRpcHeaders(false, config, JsonRpcError, error.String()) + } + + return types.ActionContinue +} + +func processToolListResponse(ctx wrapper.HttpContext, config any, tools gjson.Result, rawBody []byte) types.Action { + if isPreRequestStage(config) { + return types.ActionContinue + } + + if !isMethodAllowed(config, MethodToolList) { + log.Debugf("[ToolListResponse] Method %s is not allowed, skipping processing", MethodToolList) + return types.ActionContinue + } + + // JsonRpcResult + insertJsonRpcHeaders(false, config, JsonRpcResult, tools.String()) + + return types.ActionContinue +} + +func processToolCallRequest(ctx wrapper.HttpContext, config any, toolName string, toolArgs gjson.Result, rawBody []byte) types.Action { + if isPreResponseStage(config) { + return types.ActionContinue + } + + if !isMethodAllowed(config, MethodToolCall) { + log.Debugf("[ToolCallRequest] Method %s is not allowed, skipping processing", MethodToolCall) + return types.ActionContinue + } + + // McpToolName, McpToolArguments + insertJsonRpcHeaders(true, config, McpToolName, toolName) + insertJsonRpcHeaders(true, config, McpToolArguments, toolArgs.String()) + + return types.ActionContinue +} + +func processToolCallResponse(ctx wrapper.HttpContext, config any, isError bool, content gjson.Result, rawBody []byte) types.Action { + if isPreRequestStage(config) { + return types.ActionContinue + } + + if !isMethodAllowed(config, MethodToolCall) { + log.Debugf("[ToolCallResponse] Method %s is not allowed, skipping processing", MethodToolCall) + return types.ActionContinue + } + + // McpToolResponse, McpToolError + insertJsonRpcHeaders(false, config, McpToolResponse, content.String()) + insertJsonRpcHeaders(false, config, McpToolError, strconv.FormatBool(isError)) + + return types.ActionContinue +} diff --git a/plugins/wasm-go/extensions/jsonrpc-converter/main_test.go b/plugins/wasm-go/extensions/jsonrpc-converter/main_test.go new file mode 100644 index 000000000..dc5ff7764 --- /dev/null +++ b/plugins/wasm-go/extensions/jsonrpc-converter/main_test.go @@ -0,0 +1,28 @@ +package main + +import ( + "testing" +) + +func TestTruncateString(t *testing.T) { + tests := []struct { + name string + input string + maxLen int + expected string + }{ + {"Short String", "Higress Is an AI-Native API Gateway", 1000, "Higress Is an AI-Native API Gateway"}, + {"Exact Length", "Higress Is an AI-Native API Gateway", 35, "Higress Is an AI-Native API Gateway"}, + {"Truncated String", "Higress Is an AI-Native API Gateway", 20, "Higress Is...(truncated)...PI Gateway"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + config := McpConverterConfig{MaxHeaderLength: tt.maxLen} + result := truncateString(tt.input, config) + if result != tt.expected { + t.Errorf("truncateString(%q, %d) = %q; want %q", tt.input, tt.maxLen, result, tt.expected) + } + }) + } +} diff --git a/plugins/wasm-go/mcp-filters/mcp-router/go.mod b/plugins/wasm-go/mcp-filters/mcp-router/go.mod index d553b98ae..5e654c943 100644 --- a/plugins/wasm-go/mcp-filters/mcp-router/go.mod +++ b/plugins/wasm-go/mcp-filters/mcp-router/go.mod @@ -4,7 +4,7 @@ go 1.24.1 require ( github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250611100342-5654e89a7a80 - github.com/higress-group/wasm-go v1.0.2-0.20250807064511-eb1cd98e1f57 + github.com/higress-group/wasm-go v1.0.2-0.20250819092116-2fd2b083a8e2 github.com/tidwall/gjson v1.18.0 github.com/tidwall/sjson v1.2.5 ) diff --git a/plugins/wasm-go/mcp-filters/mcp-router/go.sum b/plugins/wasm-go/mcp-filters/mcp-router/go.sum index 610816df7..bf471fc50 100644 --- a/plugins/wasm-go/mcp-filters/mcp-router/go.sum +++ b/plugins/wasm-go/mcp-filters/mcp-router/go.sum @@ -24,6 +24,8 @@ github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250611100342-5654e89a7a80 h1 github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250611100342-5654e89a7a80/go.mod h1:tRI2LfMudSkKHhyv1uex3BWzcice2s/l8Ah8axporfA= github.com/higress-group/wasm-go v1.0.2-0.20250807064511-eb1cd98e1f57 h1:WhNdnKSDtAQrh4Yil8HAtbl7VW+WC85m7WS8kirnHAA= github.com/higress-group/wasm-go v1.0.2-0.20250807064511-eb1cd98e1f57/go.mod h1:9k7L730huS/q4V5iH9WLDgf5ZUHEtfhM/uXcegKDG/M= +github.com/higress-group/wasm-go v1.0.2-0.20250819092116-2fd2b083a8e2 h1:2wlbNpFJCQNbPBFYgswz7Zvxo9O3L0PH0AJxwiCc5lk= +github.com/higress-group/wasm-go v1.0.2-0.20250819092116-2fd2b083a8e2/go.mod h1:9k7L730huS/q4V5iH9WLDgf5ZUHEtfhM/uXcegKDG/M= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E= diff --git a/plugins/wasm-go/mcp-filters/mcp-router/main.go b/plugins/wasm-go/mcp-filters/mcp-router/main.go index 2672a6526..49003be9c 100644 --- a/plugins/wasm-go/mcp-filters/mcp-router/main.go +++ b/plugins/wasm-go/mcp-filters/mcp-router/main.go @@ -100,7 +100,7 @@ func ProcessRequest(context wrapper.HttpContext, config any, toolName string, to if !routerConfig.enable { return types.ActionContinue } - // Extract server name from tool name (format: "${serverName}HigressRouteTo${toolName}") + // Extract server name from tool name (format: "${serverName}___${toolName}") parts := strings.SplitN(toolName, consts.ToolSetNameSplitter, 2) if len(parts) != 2 { log.Debugf("Tool name '%s' does not contain server prefix, continuing without routing", toolName) diff --git a/plugins/wasm-go/mcp-servers/all-in-one/go.mod b/plugins/wasm-go/mcp-servers/all-in-one/go.mod index df19148a0..66a982534 100644 --- a/plugins/wasm-go/mcp-servers/all-in-one/go.mod +++ b/plugins/wasm-go/mcp-servers/all-in-one/go.mod @@ -8,7 +8,7 @@ replace amap-tools => ../amap-tools require ( amap-tools v0.0.0-00010101000000-000000000000 - github.com/higress-group/wasm-go v1.0.2-0.20250819032348-2591c1c2476d + github.com/higress-group/wasm-go v1.0.2-0.20250819092116-2fd2b083a8e2 quark-search v0.0.0-00010101000000-000000000000 ) diff --git a/plugins/wasm-go/mcp-servers/all-in-one/go.sum b/plugins/wasm-go/mcp-servers/all-in-one/go.sum index 06382c74e..ae3f0a9ec 100644 --- a/plugins/wasm-go/mcp-servers/all-in-one/go.sum +++ b/plugins/wasm-go/mcp-servers/all-in-one/go.sum @@ -24,6 +24,8 @@ github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250611100342-5654e89a7a80 h1 github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250611100342-5654e89a7a80/go.mod h1:tRI2LfMudSkKHhyv1uex3BWzcice2s/l8Ah8axporfA= github.com/higress-group/wasm-go v1.0.2-0.20250819032348-2591c1c2476d h1:9XEF+IifLNcEqRTPQc9o6zqVEO6NXzjHUWM6S3qpbss= github.com/higress-group/wasm-go v1.0.2-0.20250819032348-2591c1c2476d/go.mod h1:9k7L730huS/q4V5iH9WLDgf5ZUHEtfhM/uXcegKDG/M= +github.com/higress-group/wasm-go v1.0.2-0.20250819092116-2fd2b083a8e2 h1:2wlbNpFJCQNbPBFYgswz7Zvxo9O3L0PH0AJxwiCc5lk= +github.com/higress-group/wasm-go v1.0.2-0.20250819092116-2fd2b083a8e2/go.mod h1:9k7L730huS/q4V5iH9WLDgf5ZUHEtfhM/uXcegKDG/M= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=