Compare commits

...

18 Commits

Author SHA1 Message Date
johnlanni
07154d1f49 set mcp-go dependency to 0.12 2025-04-28 23:02:28 +08:00
johnlanni
db30c0962a update go mod 2025-04-28 22:04:21 +08:00
johnlanni
731fe43d14 update envoy-release version 2025-04-28 21:59:30 +08:00
EricaLiu
5bd20aa559 feat : support mcp server auto discovery for nacos registry (#2122)
Co-authored-by: 澄潭 <zty98751@alibaba-inc.com>
2025-04-28 21:58:17 +08:00
johnlanni
a2e4f944e9 rel 2.1.2-rc.1 2025-04-28 20:39:02 +08:00
johnlanni
7955aec639 change golang-filter build image 2025-04-28 19:43:17 +08:00
johnlanni
e12feb9f57 golang-filter use go 1.22 2025-04-28 19:21:09 +08:00
zty98751
03b4144cff update submodule commit 2025-04-28 17:42:58 +08:00
Jingze
c382635e7f fix: Refactor MCP Server into MCP Session and MCP Server (#2120) 2025-04-28 13:42:14 +08:00
007gzs
e381806ba0 fix: ai_data_masking add compatibility handling for non-compliant API response structures (#2130) 2025-04-27 14:31:02 +08:00
johnlanni
52114b37f8 update mcp server config fields in mcp-bridge api 2025-04-27 11:10:08 +08:00
mirror
b4e68c02f9 add mcp yuque descriptions (#2125) 2025-04-25 18:08:42 +08:00
Tsukilc
c241ccf19d test: add test for /pkg/ingress/kube/common (#2123) 2025-04-24 20:03:57 +08:00
澄潭
e4fa1e6390 Update README_ZH.md 2025-04-24 19:08:40 +08:00
澄潭
b103b9d7cb Update README.md 2025-04-24 19:08:10 +08:00
johnlanni
90b02a90e0 update mcpbridge proto 2025-04-24 15:52:17 +08:00
mirror
38f718b965 update github & e2bdev mcp descriptions (#2107) 2025-04-23 20:08:21 +08:00
johnlanni
8752a763c2 update all-in-one mcp-server 2025-04-23 14:42:44 +08:00
88 changed files with 4892 additions and 712 deletions

View File

@@ -144,7 +144,7 @@ docker-buildx-push: clean-env docker.higress-buildx
export PARENT_GIT_TAG:=$(shell cat VERSION)
export PARENT_GIT_REVISION:=$(TAG)
export ENVOY_PACKAGE_URL_PATTERN?=https://github.com/higress-group/proxy/releases/download/v2.1.4/envoy-symbol-ARCH.tar.gz
export ENVOY_PACKAGE_URL_PATTERN?=https://github.com/higress-group/proxy/releases/download/v2.1.5/envoy-symbol-ARCH.tar.gz
build-envoy: prebuild
./tools/hack/build-envoy.sh

View File

@@ -11,7 +11,8 @@
[![Build Status](https://github.com/alibaba/higress/actions/workflows/build-and-test.yaml/badge.svg?branch=main)](https://github.com/alibaba/higress/actions)
[![license](https://img.shields.io/github/license/alibaba/higress.svg)](https://www.apache.org/licenses/LICENSE-2.0.html)
<a href="https://trendshift.io/repositories/10918" target="_blank"><img src="https://trendshift.io/api/badge/repositories/10918" alt="alibaba%2Fhigress | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
<a href="https://trendshift.io/repositories/10918" target="_blank"><img src="https://trendshift.io/api/badge/repositories/10918" alt="alibaba%2Fhigress | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a> <a href="https://www.producthunt.com/posts/higress?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-higress" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=951287&theme=light&t=1745492822283" alt="Higress - Global&#0032;APIs&#0032;as&#0032;MCP&#0032;powered&#0032;by&#0032;AI&#0032;Gateway | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
</div>
[**Official Site**](https://higress.ai/en/) &nbsp; |

View File

@@ -11,7 +11,7 @@
[![Build Status](https://github.com/alibaba/higress/actions/workflows/build-and-test.yaml/badge.svg?branch=main)](https://github.com/alibaba/higress/actions)
[![license](https://img.shields.io/github/license/alibaba/higress.svg)](https://www.apache.org/licenses/LICENSE-2.0.html)
<a href="https://trendshift.io/repositories/10918" target="_blank"><img src="https://trendshift.io/api/badge/repositories/10918" alt="alibaba%2Fhigress | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
<a href="https://trendshift.io/repositories/10918" target="_blank"><img src="https://trendshift.io/api/badge/repositories/10918" alt="alibaba%2Fhigress | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a> <a href="https://www.producthunt.com/posts/higress?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-higress" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=951287&theme=light&t=1745492822283" alt="Higress - Global&#0032;APIs&#0032;as&#0032;MCP&#0032;powered&#0032;by&#0032;AI&#0032;Gateway | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
</div>
[**官网**](https://higress.cn/) &nbsp; |

View File

@@ -1 +1 @@
v2.1.1
v2.1.2-rc.1

View File

@@ -263,6 +263,14 @@ spec:
type: string
domain:
type: string
enableMCPServer:
type: boolean
mcpServerBaseUrl:
type: string
mcpServerExportDomains:
items:
type: string
type: array
nacosAccessKey:
type: string
nacosAddressServer:

View File

@@ -26,6 +26,8 @@
package v1
import (
_ "github.com/golang/protobuf/ptypes/struct"
wrappers "github.com/golang/protobuf/ptypes/wrappers"
_ "google.golang.org/genproto/googleapis/api/annotations"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
@@ -109,25 +111,28 @@ type RegistryConfig struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
Domain string `protobuf:"bytes,3,opt,name=domain,proto3" json:"domain,omitempty"`
Port uint32 `protobuf:"varint,4,opt,name=port,proto3" json:"port,omitempty"`
NacosAddressServer string `protobuf:"bytes,5,opt,name=nacosAddressServer,proto3" json:"nacosAddressServer,omitempty"`
NacosAccessKey string `protobuf:"bytes,6,opt,name=nacosAccessKey,proto3" json:"nacosAccessKey,omitempty"`
NacosSecretKey string `protobuf:"bytes,7,opt,name=nacosSecretKey,proto3" json:"nacosSecretKey,omitempty"`
NacosNamespaceId string `protobuf:"bytes,8,opt,name=nacosNamespaceId,proto3" json:"nacosNamespaceId,omitempty"`
NacosNamespace string `protobuf:"bytes,9,opt,name=nacosNamespace,proto3" json:"nacosNamespace,omitempty"`
NacosGroups []string `protobuf:"bytes,10,rep,name=nacosGroups,proto3" json:"nacosGroups,omitempty"`
NacosRefreshInterval int64 `protobuf:"varint,11,opt,name=nacosRefreshInterval,proto3" json:"nacosRefreshInterval,omitempty"`
ConsulNamespace string `protobuf:"bytes,12,opt,name=consulNamespace,proto3" json:"consulNamespace,omitempty"`
ZkServicesPath []string `protobuf:"bytes,13,rep,name=zkServicesPath,proto3" json:"zkServicesPath,omitempty"`
ConsulDatacenter string `protobuf:"bytes,14,opt,name=consulDatacenter,proto3" json:"consulDatacenter,omitempty"`
ConsulServiceTag string `protobuf:"bytes,15,opt,name=consulServiceTag,proto3" json:"consulServiceTag,omitempty"`
ConsulRefreshInterval int64 `protobuf:"varint,16,opt,name=consulRefreshInterval,proto3" json:"consulRefreshInterval,omitempty"`
AuthSecretName string `protobuf:"bytes,17,opt,name=authSecretName,proto3" json:"authSecretName,omitempty"`
Protocol string `protobuf:"bytes,18,opt,name=protocol,proto3" json:"protocol,omitempty"`
Sni string `protobuf:"bytes,19,opt,name=sni,proto3" json:"sni,omitempty"`
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
Domain string `protobuf:"bytes,3,opt,name=domain,proto3" json:"domain,omitempty"`
Port uint32 `protobuf:"varint,4,opt,name=port,proto3" json:"port,omitempty"`
NacosAddressServer string `protobuf:"bytes,5,opt,name=nacosAddressServer,proto3" json:"nacosAddressServer,omitempty"`
NacosAccessKey string `protobuf:"bytes,6,opt,name=nacosAccessKey,proto3" json:"nacosAccessKey,omitempty"`
NacosSecretKey string `protobuf:"bytes,7,opt,name=nacosSecretKey,proto3" json:"nacosSecretKey,omitempty"`
NacosNamespaceId string `protobuf:"bytes,8,opt,name=nacosNamespaceId,proto3" json:"nacosNamespaceId,omitempty"`
NacosNamespace string `protobuf:"bytes,9,opt,name=nacosNamespace,proto3" json:"nacosNamespace,omitempty"`
NacosGroups []string `protobuf:"bytes,10,rep,name=nacosGroups,proto3" json:"nacosGroups,omitempty"`
NacosRefreshInterval int64 `protobuf:"varint,11,opt,name=nacosRefreshInterval,proto3" json:"nacosRefreshInterval,omitempty"`
ConsulNamespace string `protobuf:"bytes,12,opt,name=consulNamespace,proto3" json:"consulNamespace,omitempty"`
ZkServicesPath []string `protobuf:"bytes,13,rep,name=zkServicesPath,proto3" json:"zkServicesPath,omitempty"`
ConsulDatacenter string `protobuf:"bytes,14,opt,name=consulDatacenter,proto3" json:"consulDatacenter,omitempty"`
ConsulServiceTag string `protobuf:"bytes,15,opt,name=consulServiceTag,proto3" json:"consulServiceTag,omitempty"`
ConsulRefreshInterval int64 `protobuf:"varint,16,opt,name=consulRefreshInterval,proto3" json:"consulRefreshInterval,omitempty"`
AuthSecretName string `protobuf:"bytes,17,opt,name=authSecretName,proto3" json:"authSecretName,omitempty"`
Protocol string `protobuf:"bytes,18,opt,name=protocol,proto3" json:"protocol,omitempty"`
Sni string `protobuf:"bytes,19,opt,name=sni,proto3" json:"sni,omitempty"`
McpServerExportDomains []string `protobuf:"bytes,20,rep,name=mcpServerExportDomains,proto3" json:"mcpServerExportDomains,omitempty"`
McpServerBaseUrl string `protobuf:"bytes,21,opt,name=mcpServerBaseUrl,proto3" json:"mcpServerBaseUrl,omitempty"`
EnableMCPServer *wrappers.BoolValue `protobuf:"bytes,22,opt,name=enableMCPServer,proto3" json:"enableMCPServer,omitempty"`
}
func (x *RegistryConfig) Reset() {
@@ -295,6 +300,27 @@ func (x *RegistryConfig) GetSni() string {
return ""
}
func (x *RegistryConfig) GetMcpServerExportDomains() []string {
if x != nil {
return x.McpServerExportDomains
}
return nil
}
func (x *RegistryConfig) GetMcpServerBaseUrl() string {
if x != nil {
return x.McpServerBaseUrl
}
return ""
}
func (x *RegistryConfig) GetEnableMCPServer() *wrappers.BoolValue {
if x != nil {
return x.EnableMCPServer
}
return nil
}
var File_networking_v1_mcp_bridge_proto protoreflect.FileDescriptor
var file_networking_v1_mcp_bridge_proto_rawDesc = []byte{
@@ -303,61 +329,76 @@ var file_networking_v1_mcp_bridge_proto_rawDesc = []byte{
0x12, 0x15, 0x68, 0x69, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72,
0x6b, 0x69, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f,
0x61, 0x70, 0x69, 0x2f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x62, 0x65, 0x68, 0x61, 0x76, 0x69,
0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x52, 0x0a, 0x09, 0x4d, 0x63, 0x70, 0x42,
0x72, 0x69, 0x64, 0x67, 0x65, 0x12, 0x45, 0x0a, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72,
0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x68, 0x69, 0x67, 0x72,
0x65, 0x73, 0x73, 0x2e, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x2e, 0x76,
0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
0x52, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x69, 0x65, 0x73, 0x22, 0xd3, 0x05, 0x0a,
0x0e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
0x17, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0,
0x41, 0x02, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x06,
0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41,
0x02, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x17, 0x0a, 0x04, 0x70, 0x6f, 0x72,
0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x04, 0x70, 0x6f,
0x72, 0x74, 0x12, 0x2e, 0x0a, 0x12, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x41, 0x64, 0x64, 0x72, 0x65,
0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12,
0x6e, 0x61, 0x63, 0x6f, 0x73, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76,
0x65, 0x72, 0x12, 0x26, 0x0a, 0x0e, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x41, 0x63, 0x63, 0x65, 0x73,
0x73, 0x4b, 0x65, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6e, 0x61, 0x63, 0x6f,
0x73, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x0e, 0x6e, 0x61,
0x63, 0x6f, 0x73, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x18, 0x07, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0e, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4b,
0x65, 0x79, 0x12, 0x2a, 0x0a, 0x10, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73,
0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6e, 0x61,
0x63, 0x6f, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x26,
0x0a, 0x0e, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65,
0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x4e, 0x61, 0x6d,
0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x47,
0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x6e, 0x61, 0x63,
0x6f, 0x73, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x32, 0x0a, 0x14, 0x6e, 0x61, 0x63, 0x6f,
0x73, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c,
0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x52, 0x65, 0x66,
0x72, 0x65, 0x73, 0x68, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x28, 0x0a, 0x0f,
0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18,
0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x4e, 0x61, 0x6d,
0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x26, 0x0a, 0x0e, 0x7a, 0x6b, 0x53, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x73, 0x50, 0x61, 0x74, 0x68, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e,
0x7a, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x50, 0x61, 0x74, 0x68, 0x12, 0x2a,
0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x63, 0x65, 0x6e, 0x74,
0x65, 0x72, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c,
0x44, 0x61, 0x74, 0x61, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x12, 0x2a, 0x0a, 0x10, 0x63, 0x6f,
0x6e, 0x73, 0x75, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x54, 0x61, 0x67, 0x18, 0x0f,
0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x53, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x54, 0x61, 0x67, 0x12, 0x34, 0x0a, 0x15, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c,
0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18,
0x10, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x52, 0x65, 0x66,
0x72, 0x65, 0x73, 0x68, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x26, 0x0a, 0x0e,
0x61, 0x75, 0x74, 0x68, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x11,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74,
0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c,
0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c,
0x12, 0x10, 0x0a, 0x03, 0x73, 0x6e, 0x69, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x73,
0x6e, 0x69, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
0x2f, 0x61, 0x6c, 0x69, 0x62, 0x61, 0x62, 0x61, 0x2f, 0x68, 0x69, 0x67, 0x72, 0x65, 0x73, 0x73,
0x2f, 0x61, 0x70, 0x69, 0x2f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x2f,
0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65,
0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x52, 0x0a, 0x09, 0x4d, 0x63, 0x70, 0x42, 0x72, 0x69,
0x64, 0x67, 0x65, 0x12, 0x45, 0x0a, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x69, 0x65,
0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x68, 0x69, 0x67, 0x72, 0x65, 0x73,
0x73, 0x2e, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e,
0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0a,
0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x69, 0x65, 0x73, 0x22, 0xfd, 0x06, 0x0a, 0x0e, 0x52,
0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x17, 0x0a,
0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02,
0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x06, 0x64, 0x6f,
0x6d, 0x61, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52,
0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x17, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18,
0x04, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74,
0x12, 0x2e, 0x0a, 0x12, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x6e, 0x61,
0x63, 0x6f, 0x73, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
0x12, 0x26, 0x0a, 0x0e, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4b,
0x65, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x41,
0x63, 0x63, 0x65, 0x73, 0x73, 0x4b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x0e, 0x6e, 0x61, 0x63, 0x6f,
0x73, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09,
0x52, 0x0e, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4b, 0x65, 0x79,
0x12, 0x2a, 0x0a, 0x10, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61,
0x63, 0x65, 0x49, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6e, 0x61, 0x63, 0x6f,
0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0e,
0x6e, 0x61, 0x63, 0x6f, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x09,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73,
0x70, 0x61, 0x63, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x47, 0x72, 0x6f,
0x75, 0x70, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x6e, 0x61, 0x63, 0x6f, 0x73,
0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x32, 0x0a, 0x14, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x52,
0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x0b,
0x20, 0x01, 0x28, 0x03, 0x52, 0x14, 0x6e, 0x61, 0x63, 0x6f, 0x73, 0x52, 0x65, 0x66, 0x72, 0x65,
0x73, 0x68, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x28, 0x0a, 0x0f, 0x63, 0x6f,
0x6e, 0x73, 0x75, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x0c, 0x20,
0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x73,
0x70, 0x61, 0x63, 0x65, 0x12, 0x26, 0x0a, 0x0e, 0x7a, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
0x65, 0x73, 0x50, 0x61, 0x74, 0x68, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x7a, 0x6b,
0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x50, 0x61, 0x74, 0x68, 0x12, 0x2a, 0x0a, 0x10,
0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72,
0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x44, 0x61,
0x74, 0x61, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x12, 0x2a, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x73,
0x75, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x54, 0x61, 0x67, 0x18, 0x0f, 0x20, 0x01,
0x28, 0x09, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
0x65, 0x54, 0x61, 0x67, 0x12, 0x34, 0x0a, 0x15, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x52, 0x65,
0x66, 0x72, 0x65, 0x73, 0x68, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x10, 0x20,
0x01, 0x28, 0x03, 0x52, 0x15, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x52, 0x65, 0x66, 0x72, 0x65,
0x73, 0x68, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x26, 0x0a, 0x0e, 0x61, 0x75,
0x74, 0x68, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x11, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4e, 0x61,
0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x12,
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x10,
0x0a, 0x03, 0x73, 0x6e, 0x69, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x73, 0x6e, 0x69,
0x12, 0x36, 0x0a, 0x16, 0x6d, 0x63, 0x70, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x78, 0x70,
0x6f, 0x72, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x14, 0x20, 0x03, 0x28, 0x09,
0x52, 0x16, 0x6d, 0x63, 0x70, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x78, 0x70, 0x6f, 0x72,
0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x2a, 0x0a, 0x10, 0x6d, 0x63, 0x70, 0x53,
0x65, 0x72, 0x76, 0x65, 0x72, 0x42, 0x61, 0x73, 0x65, 0x55, 0x72, 0x6c, 0x18, 0x15, 0x20, 0x01,
0x28, 0x09, 0x52, 0x10, 0x6d, 0x63, 0x70, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x42, 0x61, 0x73,
0x65, 0x55, 0x72, 0x6c, 0x12, 0x44, 0x0a, 0x0f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x4d, 0x43,
0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x16, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0f, 0x65, 0x6e, 0x61, 0x62, 0x6c,
0x65, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69,
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x6c, 0x69, 0x62, 0x61, 0x62, 0x61,
0x2f, 0x68, 0x69, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x6e, 0x65, 0x74,
0x77, 0x6f, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x33,
}
var (
@@ -374,16 +415,18 @@ func file_networking_v1_mcp_bridge_proto_rawDescGZIP() []byte {
var file_networking_v1_mcp_bridge_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_networking_v1_mcp_bridge_proto_goTypes = []interface{}{
(*McpBridge)(nil), // 0: higress.networking.v1.McpBridge
(*RegistryConfig)(nil), // 1: higress.networking.v1.RegistryConfig
(*McpBridge)(nil), // 0: higress.networking.v1.McpBridge
(*RegistryConfig)(nil), // 1: higress.networking.v1.RegistryConfig
(*wrappers.BoolValue)(nil), // 2: google.protobuf.BoolValue
}
var file_networking_v1_mcp_bridge_proto_depIdxs = []int32{
1, // 0: higress.networking.v1.McpBridge.registries:type_name -> higress.networking.v1.RegistryConfig
1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
2, // 1: higress.networking.v1.RegistryConfig.enableMCPServer:type_name -> google.protobuf.BoolValue
2, // [2:2] is the sub-list for method output_type
2, // [2:2] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
}
func init() { file_networking_v1_mcp_bridge_proto_init() }

View File

@@ -15,6 +15,8 @@
syntax = "proto3";
import "google/api/field_behavior.proto";
import "google/protobuf/wrappers.proto";
import "google/protobuf/struct.proto";
// $schema: higress.networking.v1.McpBridge
// $title: McpBridge
@@ -66,4 +68,7 @@ message RegistryConfig {
string authSecretName = 17;
string protocol = 18;
string sni = 19;
repeated string mcpServerExportDomains = 20;
string mcpServerBaseUrl = 21;
google.protobuf.BoolValue enableMCPServer = 22;
}

39
go.mod
View File

@@ -39,7 +39,7 @@ require (
github.com/tidwall/gjson v1.17.0
go.uber.org/atomic v1.11.0
go.uber.org/zap v1.27.0
golang.org/x/net v0.27.0
golang.org/x/net v0.33.0
google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13
google.golang.org/grpc v1.59.0
google.golang.org/protobuf v1.33.0
@@ -71,7 +71,27 @@ require (
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
github.com/alecholmes/xfccparser v0.1.0 // indirect
github.com/alecthomas/participle v0.4.1 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1704 // indirect
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6 // indirect
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
github.com/alibabacloud-go/darabonba-array v0.1.0 // indirect
github.com/alibabacloud-go/darabonba-encode-util v0.0.2 // indirect
github.com/alibabacloud-go/darabonba-map v0.0.2 // indirect
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10 // indirect
github.com/alibabacloud-go/darabonba-signature-util v0.0.7 // indirect
github.com/alibabacloud-go/darabonba-string v1.0.2 // indirect
github.com/alibabacloud-go/debug v1.0.1 // indirect
github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect
github.com/alibabacloud-go/kms-20160120/v3 v3.2.3 // indirect
github.com/alibabacloud-go/openapi-util v0.1.0 // indirect
github.com/alibabacloud-go/tea v1.2.2 // indirect
github.com/alibabacloud-go/tea-utils v1.4.4 // indirect
github.com/alibabacloud-go/tea-utils/v2 v2.0.7 // indirect
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1800 // indirect
github.com/aliyun/alibabacloud-dkms-gcs-go-sdk v0.5.1 // indirect
github.com/aliyun/alibabacloud-dkms-transfer-go-sdk v0.1.8 // indirect
github.com/aliyun/aliyun-secretsmanager-client-go v1.1.5 // indirect
github.com/aliyun/credentials-go v1.4.3 // indirect
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df // indirect
github.com/armon/go-metrics v0.4.1 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
@@ -82,10 +102,12 @@ require (
github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/clbanning/mxj v1.8.4 // indirect
github.com/clbanning/mxj/v2 v2.5.5 // indirect
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
github.com/coreos/go-oidc/v3 v3.6.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/deckarep/golang-set v1.7.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/docker/cli v24.0.7+incompatible // indirect
github.com/docker/distribution v2.8.2+incompatible // indirect
@@ -165,6 +187,7 @@ require (
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc5 // indirect
github.com/openshift/api v0.0.0-20230720094506-afcbe27aec7c // indirect
github.com/orcaman/concurrent-map v0.0.0-20210501183033-44dafcb38ecc // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
@@ -182,6 +205,7 @@ require (
github.com/tetratelabs/wazero v1.7.3 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3 // indirect
github.com/vbatts/tar-split v0.11.3 // indirect
github.com/xlab/treeprint v1.2.0 // indirect
@@ -197,14 +221,14 @@ require (
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/oauth2 v0.13.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/term v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/term v0.27.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
@@ -250,5 +274,6 @@ replace github.com/caddyserver/certmagic => github.com/2456868764/certmagic v1.0
replace (
github.com/dubbogo/gost => github.com/johnlanni/gost v1.11.23-0.20220713132522-0967a24036c6
github.com/nacos-group/nacos-sdk-go/v2 => github.com/luoxiner/nacos-sdk-go/v2 v2.2.9-60
golang.org/x/exp => golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1
)

144
go.sum
View File

@@ -683,9 +683,68 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/alessio/shellescape v1.2.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6 h1:eIf+iGJxdU4U9ypaUfbtOWCsZSbTb8AUHvyPrxu6mAA=
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do24zMOGGqYVWgw0s9NtiylnJglOeEB5UJo=
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 h1:zE8vH9C7JiZLNJJQ5OwjU9mSi4T9ef9u3BURT6LCLC8=
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5/go.mod h1:tWnyE9AjF8J8qqLk645oUmVUnFybApTQWklQmi5tY6g=
github.com/alibabacloud-go/darabonba-array v0.1.0 h1:vR8s7b1fWAQIjEjWnuF0JiKsCvclSRTfDzZHTYqfufY=
github.com/alibabacloud-go/darabonba-array v0.1.0/go.mod h1:BLKxr0brnggqOJPqT09DFJ8g3fsDshapUD3C3aOEFaI=
github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC2NG0Ax+GpOM5gtupki31XE=
github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F4PKuMgEUETNZasrDM6vqVr/Can7H8=
github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc=
github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc=
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.9/go.mod h1:bb+Io8Sn2RuM3/Rpme6ll86jMyFSrD1bxeV/+v61KeU=
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10 h1:GEYkMApgpKEVDn6z12DcH1EGYpDYRB8JxsazM4Rywak=
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10/go.mod h1:26a14FGhZVELuz2cc2AolvW4RHmIO3/HRwsdHhaIPDE=
github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg=
github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ=
github.com/alibabacloud-go/darabonba-string v1.0.2 h1:E714wms5ibdzCqGeYJ9JCFywE5nDyvIXIIQbZVFkkqo=
github.com/alibabacloud-go/darabonba-string v1.0.2/go.mod h1:93cTfV3vuPhhEwGGpKKqhVW4jLe7tDpo3LUM0i0g6mA=
github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY=
github.com/alibabacloud-go/debug v1.0.0/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc=
github.com/alibabacloud-go/debug v1.0.1 h1:MsW9SmUtbb1Fnt3ieC6NNZi6aEwrXfDksD4QA6GSbPg=
github.com/alibabacloud-go/debug v1.0.1/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc=
github.com/alibabacloud-go/endpoint-util v1.1.0 h1:r/4D3VSw888XGaeNpP994zDUaxdgTSHBbVfZlzf6b5Q=
github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE=
github.com/alibabacloud-go/kms-20160120/v3 v3.2.3 h1:vamGcYQFwXVqR6RWcrVTTqlIXZVsYjaA7pZbx+Xw6zw=
github.com/alibabacloud-go/kms-20160120/v3 v3.2.3/go.mod h1:3rIyughsFDLie1ut9gQJXkWkMg/NfXBCk+OtXnPu3lw=
github.com/alibabacloud-go/openapi-util v0.1.0 h1:0z75cIULkDrdEhkLWgi9tnLe+KhAFE/r5Pb3312/eAY=
github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws=
github.com/alibabacloud-go/tea v1.1.0/go.mod h1:IkGyUSX4Ba1V+k4pCtJUc6jDpZLFph9QMy2VUPTwukg=
github.com/alibabacloud-go/tea v1.1.7/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
github.com/alibabacloud-go/tea v1.1.8/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
github.com/alibabacloud-go/tea v1.2.1/go.mod h1:qbzof29bM/IFhLMtJPrgTGK3eauV5J2wSyEUo4OEmnA=
github.com/alibabacloud-go/tea v1.2.2 h1:aTsR6Rl3ANWPfqeQugPglfurloyBJY85eFy7Gc1+8oU=
github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk=
github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE=
github.com/alibabacloud-go/tea-utils v1.4.4 h1:lxCDvNCdTo9FaXKKq45+4vGETQUKNOW/qKTcX9Sk53o=
github.com/alibabacloud-go/tea-utils v1.4.4/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw=
github.com/alibabacloud-go/tea-utils/v2 v2.0.3/go.mod h1:sj1PbjPodAVTqGTA3olprfeeqqmwD0A5OQz94o9EuXQ=
github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4=
github.com/alibabacloud-go/tea-utils/v2 v2.0.6/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I=
github.com/alibabacloud-go/tea-utils/v2 v2.0.7 h1:WDx5qW3Xa5ZgJ1c8NfqJkF6w+AU5wB8835UdhPr6Ax0=
github.com/alibabacloud-go/tea-utils/v2 v2.0.7/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I=
github.com/alibabacloud-go/tea-xml v1.1.3 h1:7LYnm+JbOq2B+T/B0fHC4Ies4/FofC4zHzYtqw7dgt0=
github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1704 h1:PpfENOj/vPfhhy9N2OFRjpue0hjM5XqAp2thFmkXXIk=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1704/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1800 h1:ie/8RxBOfKZWcrbYSJi2Z8uX8TcOlSMwPlEJh83OeOw=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1800/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU=
github.com/aliyun/alibabacloud-dkms-gcs-go-sdk v0.5.1 h1:nJYyoFP+aqGKgPs9JeZgS1rWQ4NndNR0Zfhh161ZltU=
github.com/aliyun/alibabacloud-dkms-gcs-go-sdk v0.5.1/go.mod h1:WzGOmFFTlUzXM03CJnHWMQ85UN6QGpOXZocCjwkiyOg=
github.com/aliyun/alibabacloud-dkms-transfer-go-sdk v0.1.8 h1:QeUdR7JF7iNCvO/81EhxEr3wDwxk4YBoYZOq6E0AjHI=
github.com/aliyun/alibabacloud-dkms-transfer-go-sdk v0.1.8/go.mod h1:xP0KIZry6i7oGPF24vhAPr1Q8vLZRcMcxtft5xDKwCU=
github.com/aliyun/aliyun-secretsmanager-client-go v1.1.5 h1:8S0mtD101RDYa0LXwdoqgN0RxdMmmJYjq8g2mk7/lQ4=
github.com/aliyun/aliyun-secretsmanager-client-go v1.1.5/go.mod h1:M19fxYz3gpm0ETnoKweYyYtqrtnVtrpKFpwsghbw+cQ=
github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw=
github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0=
github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM=
github.com/aliyun/credentials-go v1.3.10/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U=
github.com/aliyun/credentials-go v1.4.3 h1:N3iHyvHRMyOwY1+0qBLSf3hb5JFiOujVSVuEpgeGttY=
github.com/aliyun/credentials-go v1.4.3/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
@@ -755,7 +814,6 @@ github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6
github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.11-0.20170329064859-445be9e134b2/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
@@ -765,6 +823,8 @@ github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6D
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I=
github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E=
github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
@@ -813,6 +873,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ=
github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
@@ -1162,8 +1224,9 @@ github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97Dwqy
github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0=
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
@@ -1371,6 +1434,8 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhn
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/luoxiner/nacos-sdk-go/v2 v2.2.9-60 h1:FA/azfz2nSkMc1XR8LeqhcAiA/2/sOMcyBGYCTUc+Cs=
github.com/luoxiner/nacos-sdk-go/v2 v2.2.9-60/go.mod h1:9FKXl6FqOiVmm72i8kADtbeK71egyG9y3uRDBg41tpQ=
github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o=
github.com/lyft/protoc-gen-star/v2 v2.0.3/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLOSpEk//mLlc+axEk=
@@ -1460,8 +1525,6 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/nacos-group/nacos-sdk-go v1.0.8 h1:8pEm05Cdav9sQgJSv5kyvlgfz0SzFUUGI3pWX6SiSnM=
github.com/nacos-group/nacos-sdk-go v1.0.8/go.mod h1:hlAPn3UdzlxIlSILAyOXKxjFSvDJ9oLzTJ9hLAK1KzA=
github.com/nacos-group/nacos-sdk-go/v2 v2.1.2 h1:A8GV6j0rw80I6tTKSav/pTpEgNECYXeFvZCsiLBWGnQ=
github.com/nacos-group/nacos-sdk-go/v2 v2.1.2/go.mod h1:ys/1adWeKXXzbNWfRNbaFlX/t6HVLWdpsNDvmoWTw0g=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
@@ -1517,6 +1580,8 @@ github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJ
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go v0.3.0/go.mod h1:4c3sLeE8xjNqehmF5RpAFLPLJxXscc0R4l6Zg0P1tTQ=
github.com/orcaman/concurrent-map v0.0.0-20210501183033-44dafcb38ecc h1:Ak86L+yDSOzKFa7WM5bf5itSOo1e3Xh8bm5YCMUXIjQ=
github.com/orcaman/concurrent-map v0.0.0-20210501183033-44dafcb38ecc/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
@@ -1560,7 +1625,6 @@ github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3O
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
@@ -1593,7 +1657,6 @@ github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/prometheus/prometheus v0.45.0 h1:O/uG+Nw4kNxx/jDPxmjsSDd+9Ohql6E7ZSY1x5x/0KI=
@@ -1643,8 +1706,9 @@ github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.1.0 h1:MkTeG1DMwsrdH7QtLXy5W+fUxWq+vmb6cLmyJ7aRtF0=
github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
@@ -1713,6 +1777,9 @@ github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20200427203606-3cfed13b9966/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
@@ -1746,6 +1813,7 @@ github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+Seva
github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
@@ -1832,7 +1900,6 @@ go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0
go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
@@ -1849,7 +1916,6 @@ go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@@ -1868,9 +1934,12 @@ golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
@@ -1882,8 +1951,13 @@ golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw=
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
@@ -1970,6 +2044,7 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
@@ -2008,8 +2083,13 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -2059,8 +2139,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -2107,6 +2187,7 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -2156,7 +2237,6 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -2181,8 +2261,13 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -2195,8 +2280,13 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -2214,8 +2304,11 @@ golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -2225,7 +2318,6 @@ golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxb
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
@@ -2279,6 +2371,7 @@ golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjs
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
@@ -2646,6 +2739,7 @@ gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=

View File

@@ -1,5 +1,5 @@
apiVersion: v2
appVersion: 2.1.1
appVersion: 2.1.2-rc.1
description: Helm chart for deploying higress gateways
icon: https://higress.io/img/higress_logo_small.png
home: http://higress.io/
@@ -15,4 +15,4 @@ dependencies:
repository: "file://../redis"
version: 0.0.1
type: application
version: 2.1.1
version: 2.1.2-rc.1

View File

@@ -1,9 +1,9 @@
dependencies:
- name: higress-core
repository: file://../core
version: 2.1.1
version: 2.1.2-rc.1
- name: higress-console
repository: https://higress.io/helm-charts/
version: 2.1.1
digest: sha256:a40f56252d0aa995fbf62952a6d23304dd84845caf5766bc64f7270cb00f01e9
generated: "2025-04-18T16:45:23.961507+08:00"
version: 2.1.2
digest: sha256:0933dd97b646ae2e1740ccfb6606793b088abeeeb03f687a94c02fe34f227d87
generated: "2025-04-28T20:34:11.26913+08:00"

View File

@@ -1,5 +1,5 @@
apiVersion: v2
appVersion: 2.1.1
appVersion: 2.1.2-rc.1
description: Helm chart for deploying Higress gateways
icon: https://higress.io/img/higress_logo_small.png
home: http://higress.io/
@@ -12,9 +12,9 @@ sources:
dependencies:
- name: higress-core
repository: "file://../core"
version: 2.1.1
version: 2.1.2-rc.1
- name: higress-console
repository: "https://higress.io/helm-charts/"
version: 2.1.1
version: 2.1.2
type: application
version: 2.1.1
version: 2.1.2-rc.1

View File

@@ -242,15 +242,15 @@ require (
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/oauth2 v0.13.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/term v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/term v0.27.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
google.golang.org/appengine v1.6.8 // indirect

View File

@@ -1789,8 +1789,9 @@ golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw=
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
@@ -1909,8 +1910,9 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1961,8 +1963,9 @@ golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -2069,8 +2072,9 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -2086,8 +2090,9 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -2108,8 +2113,9 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=

View File

@@ -22,5 +22,6 @@ var (
GatewayName = env.RegisterStringVar("GATEWAY_NAME", "higress-gateway", "").Get()
// Revision is the value of the Istio control plane revision, e.g. "canary",
// and is the value used by the "istio.io/rev" label.
Revision = env.Register("REVISION", "", "").Get()
Revision = env.Register("REVISION", "", "").Get()
McpServerWasmImageUrl = env.RegisterStringVar("MCP_SERVER_WASM_IMAGE_URL", "oci://higress-registry.cn-hangzhou.cr.aliyuncs.com/mcp-server/all-in-one:1.0.0", "").Get()
)

View File

@@ -590,6 +590,13 @@ func (m *IngressConfig) convertVirtualService(configs []common.WrapperConfig) []
Spec: vs,
})
}
// add vs from naco3 for mcp server
if m.RegistryReconciler != nil {
allConfigsFromMcp := m.RegistryReconciler.GetAllConfigs(gvk.VirtualService)
for _, cfg := range allConfigsFromMcp {
out = append(out, *cfg)
}
}
// We generate some specific envoy filter here to avoid duplicated computation.
m.convertEnvoyFilter(&convertOptions)
@@ -676,6 +683,13 @@ func (m *IngressConfig) convertWasmPlugin([]common.WrapperConfig) []config.Confi
Spec: wasmPlugin,
})
}
// add wasm plugin from nacos for mcp server
if m.RegistryReconciler != nil {
wasmFromMcp := m.RegistryReconciler.GetAllConfigs(gvk.WasmPlugin)
for _, cfg := range wasmFromMcp {
out = append(out, *cfg)
}
}
return out
}
@@ -686,6 +700,7 @@ func (m *IngressConfig) convertServiceEntry([]common.WrapperConfig) []config.Con
serviceEntries := m.RegistryReconciler.GetAllServiceWrapper()
IngressLog.Infof("Found mcp serviceEntries %v", serviceEntries)
out := make([]config.Config, 0, len(serviceEntries))
hostSets := sets.Set[string]{}
for _, se := range serviceEntries {
out = append(out, config.Config{
Meta: config.Meta{
@@ -700,6 +715,15 @@ func (m *IngressConfig) convertServiceEntry([]common.WrapperConfig) []config.Con
},
Spec: se.ServiceEntry,
})
hostSets.Insert(se.ServiceEntry.Hosts[0])
}
// add service entry by host from nacos3 for mcp server
seFromMcp := m.RegistryReconciler.GetAllConfigs(gvk.ServiceEntry)
for _, cfg := range seFromMcp {
se := cfg.Spec.(*networking.ServiceEntry)
if !hostSets.Contains(se.Hosts[0]) {
out = append(out, *cfg)
}
}
return out
}
@@ -770,6 +794,10 @@ func (m *IngressConfig) convertDestinationRule(configs []common.WrapperConfig) [
if !exist {
destinationRules[serviceName] = destinationRuleWrapper
} else if dr.DestinationRule.TrafficPolicy != nil {
if dr.DestinationRule.TrafficPolicy.LoadBalancer == nil &&
destinationRuleWrapper.DestinationRule.TrafficPolicy.LoadBalancer != nil {
dr.DestinationRule.TrafficPolicy.LoadBalancer = destinationRuleWrapper.DestinationRule.TrafficPolicy.LoadBalancer
}
portTrafficPolicy := destinationRuleWrapper.DestinationRule.TrafficPolicy.PortLevelSettings[0]
portUpdated := false
for _, policy := range dr.DestinationRule.TrafficPolicy.PortLevelSettings {
@@ -1137,6 +1165,28 @@ func (m *IngressConfig) AddOrUpdateMcpBridge(clusterNamespacedName util.ClusterN
// Set this label so that we do not compare configs and just push.
Labels: map[string]string{constants.AlwaysPushLabel: "true"},
}
vsMetadata := config.Meta{
Name: "mcpbridge-virtualservice",
Namespace: m.namespace,
GroupVersionKind: gvk.VirtualService,
// Set this label so that we do not compare configs and just push.
Labels: map[string]string{constants.AlwaysPushLabel: "true"},
}
wasmMetadata := config.Meta{
Name: "mcpbridge-wasmplugin",
Namespace: m.namespace,
GroupVersionKind: gvk.WasmPlugin,
// Set this label so that we do not compare configs and just push.
Labels: map[string]string{constants.AlwaysPushLabel: "true"},
}
efMetadata := config.Meta{
Name: "mcpbridge-envoyfilter",
Namespace: m.namespace,
GroupVersionKind: gvk.EnvoyFilter,
// Set this label so that we do not compare configs and just push.
Labels: map[string]string{constants.AlwaysPushLabel: "true"},
}
for _, f := range m.serviceEntryHandlers {
IngressLog.Debug("McpBridge triggerd serviceEntry update")
f(config.Config{Meta: seMetadata}, config.Config{Meta: seMetadata}, istiomodel.EventUpdate)
@@ -1145,9 +1195,22 @@ func (m *IngressConfig) AddOrUpdateMcpBridge(clusterNamespacedName util.ClusterN
IngressLog.Debug("McpBridge triggerd destinationRule update")
f(config.Config{Meta: drMetadata}, config.Config{Meta: drMetadata}, istiomodel.EventUpdate)
}
}, m.localKubeClient, m.namespace)
for _, f := range m.virtualServiceHandlers {
IngressLog.Debug("McpBridge triggerd virtualservice update")
f(config.Config{Meta: vsMetadata}, config.Config{Meta: vsMetadata}, istiomodel.EventUpdate)
}
for _, f := range m.wasmPluginHandlers {
IngressLog.Debug("McpBridge triggerd wasmplugin update")
f(config.Config{Meta: wasmMetadata}, config.Config{Meta: wasmMetadata}, istiomodel.EventUpdate)
}
for _, f := range m.envoyFilterHandlers {
IngressLog.Debug("McpBridge triggerd envoyfilter update")
f(config.Config{Meta: efMetadata}, config.Config{Meta: efMetadata}, istiomodel.EventUpdate)
}
}, m.localKubeClient, m.namespace, m.clusterId.String())
}
reconciler := m.RegistryReconciler
m.configmapMgr.SetMcpReconciler(m.RegistryReconciler)
err = reconciler.Reconcile(mcpbridge)
if err != nil {
IngressLog.Errorf("Mcpbridge reconcile failed, err:%v", err)

View File

@@ -0,0 +1,97 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package common
import (
"testing"
"github.com/stretchr/testify/assert"
"istio.io/istio/pilot/pkg/model"
"istio.io/istio/pkg/config"
)
func TestIngressDomainCache(t *testing.T) {
cache := NewIngressDomainCache()
assert.NotNil(t, cache)
assert.NotNil(t, cache.Valid)
assert.Empty(t, cache.Invalid)
cache.Valid["example.com"] = &IngressDomainBuilder{
Host: "example.com",
Protocol: HTTP,
ClusterId: "cluster-1",
Ingress: &config.Config{
Meta: config.Meta{
Name: "test-ingress",
Namespace: "default",
},
},
}
cache.Invalid = append(cache.Invalid, model.IngressDomain{
Host: "invalid.com",
Error: "invalid domain",
})
result := cache.Extract()
assert.Equal(t, 1, len(result.Valid))
assert.Equal(t, "example.com", result.Valid[0].Host)
assert.Equal(t, string(HTTP), result.Valid[0].Protocol)
assert.Equal(t, 1, len(result.Invalid))
assert.Equal(t, "invalid.com", result.Invalid[0].Host)
}
func TestIngressDomainBuilder(t *testing.T) {
builder := &IngressDomainBuilder{
Host: "example.com",
Protocol: HTTP,
ClusterId: "cluster-1",
Ingress: &config.Config{
Meta: config.Meta{
Name: "test-ingress",
Namespace: "default",
},
},
}
domain := builder.Build()
assert.Equal(t, "example.com", domain.Host)
assert.Equal(t, string(HTTP), domain.Protocol)
builder.Event = MissingSecret
eventDomain := builder.Build()
assert.Contains(t, eventDomain.Error, "misses secret")
builder.Event = DuplicatedTls
builder.PreIngress = &config.Config{
Meta: config.Meta{
Name: "pre-ingress",
Namespace: "default",
},
}
builder.PreIngress.Meta.Annotations = map[string]string{
ClusterIdAnnotation: "pre-cluster",
}
dupDomain := builder.Build()
assert.Contains(t, dupDomain.Error, "conflicted with ingress")
builder.Protocol = HTTPS
builder.SecretName = "test-secret"
builder.Event = ""
httpsDomain := builder.Build()
assert.Equal(t, string(HTTPS), httpsDomain.Protocol)
assert.Equal(t, "test-secret", httpsDomain.SecretName)
}

View File

@@ -18,6 +18,7 @@ import (
"testing"
networking "istio.io/api/networking/v1alpha3"
"istio.io/istio/pilot/pkg/model"
"istio.io/istio/pkg/config"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -556,3 +557,514 @@ func TestSortHTTPRoutesWithMoreRules(t *testing.T) {
}
}
}
func TestValidateBackendResource(t *testing.T) {
groupStr := "networking.higress.io"
testCases := []struct {
name string
resource *v1.TypedLocalObjectReference
expected bool
}{
{
name: "nil resource",
resource: nil,
expected: false,
},
{
name: "nil APIGroup",
resource: &v1.TypedLocalObjectReference{
APIGroup: nil,
Kind: "McpBridge",
Name: "default",
},
expected: false,
},
{
name: "wrong APIGroup",
resource: &v1.TypedLocalObjectReference{
APIGroup: &groupStr,
Kind: "McpBridge",
Name: "wrong-name",
},
expected: false,
},
{
name: "wrong Kind",
resource: &v1.TypedLocalObjectReference{
APIGroup: &groupStr,
Kind: "WrongKind",
Name: "default",
},
expected: false,
},
{
name: "valid resource",
resource: &v1.TypedLocalObjectReference{
APIGroup: &groupStr,
Kind: "McpBridge",
Name: "default",
},
expected: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := ValidateBackendResource(tc.resource)
assert.Equal(t, tc.expected, result)
})
}
}
func TestCreateOrUpdateAnnotations(t *testing.T) {
testCases := []struct {
name string
annotations map[string]string
options Options
expected map[string]string
}{
{
name: "empty annotations",
annotations: map[string]string{},
options: Options{
ClusterId: "test-cluster",
RawClusterId: "raw-test-cluster",
},
expected: map[string]string{
ClusterIdAnnotation: "test-cluster",
RawClusterIdAnnotation: "raw-test-cluster",
},
},
{
name: "existing annotations",
annotations: map[string]string{
"key1": "value1",
"key2": "value2",
},
options: Options{
ClusterId: "test-cluster",
RawClusterId: "raw-test-cluster",
},
expected: map[string]string{
"key1": "value1",
"key2": "value2",
ClusterIdAnnotation: "test-cluster",
RawClusterIdAnnotation: "raw-test-cluster",
},
},
{
name: "overwrite existing cluster annotations",
annotations: map[string]string{
ClusterIdAnnotation: "old-cluster",
RawClusterIdAnnotation: "old-raw-cluster",
"key1": "value1",
},
options: Options{
ClusterId: "new-cluster",
RawClusterId: "new-raw-cluster",
},
expected: map[string]string{
ClusterIdAnnotation: "new-cluster",
RawClusterIdAnnotation: "new-raw-cluster",
"key1": "value1",
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := CreateOrUpdateAnnotations(tc.annotations, tc.options)
assert.Equal(t, tc.expected, result)
})
}
}
func TestGetClusterId(t *testing.T) {
testCases := []struct {
name string
annotations map[string]string
expected string
}{
{
name: "nil annotations",
annotations: nil,
expected: "",
},
{
name: "empty annotations",
annotations: map[string]string{},
expected: "",
},
{
name: "with cluster id",
annotations: map[string]string{
ClusterIdAnnotation: "test-cluster",
},
expected: "test-cluster",
},
{
name: "with other annotations",
annotations: map[string]string{
"key1": "value1",
"key2": "value2",
},
expected: "",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := GetClusterId(tc.annotations)
assert.Equal(t, tc.expected, string(result))
})
}
}
func TestConvertToDNSLabelValidAndCleanHost(t *testing.T) {
testCases := []struct {
name string
input string
}{
{
name: "simple host",
input: "example.com",
},
{
name: "wildcard host",
input: "*.example.com",
},
{
name: "long host",
input: "very-long-subdomain.example-service.my-namespace.svc.cluster.local",
},
{
name: "empty host",
input: "",
},
{
name: "ip address",
input: "192.168.1.1",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Test internal convertToDNSLabelValid function (through CleanHost)
result := CleanHost(tc.input)
// Validate result
assert.NotEmpty(t, result)
assert.Equal(t, 16, len(result)) // MD5 hash format is fixed length of 16 bytes
// Consistency check - same input should produce same output
result2 := CleanHost(tc.input)
assert.Equal(t, result, result2)
})
}
}
func TestSplitServiceFQDN(t *testing.T) {
testCases := []struct {
name string
fqdn string
expectedSvc string
expectedNs string
expectedValid bool
}{
{
name: "simple fqdn",
fqdn: "service.namespace",
expectedSvc: "service",
expectedNs: "namespace",
expectedValid: true,
},
{
name: "full k8s fqdn",
fqdn: "service.namespace.svc.cluster.local",
expectedSvc: "service",
expectedNs: "namespace",
expectedValid: true,
},
{
name: "just service name",
fqdn: "service",
expectedSvc: "",
expectedNs: "",
expectedValid: false,
},
{
name: "empty string",
fqdn: "",
expectedSvc: "",
expectedNs: "",
expectedValid: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
svc, ns, valid := SplitServiceFQDN(tc.fqdn)
assert.Equal(t, tc.expectedSvc, svc)
assert.Equal(t, tc.expectedNs, ns)
assert.Equal(t, tc.expectedValid, valid)
})
}
}
func TestConvertBackendService(t *testing.T) {
testCases := []struct {
name string
dest *networking.HTTPRouteDestination
expected model.BackendService
}{
{
name: "simple service",
dest: &networking.HTTPRouteDestination{
Destination: &networking.Destination{
Host: "service.namespace",
Port: &networking.PortSelector{
Number: 80,
},
},
Weight: 100,
},
expected: model.BackendService{
Name: "service",
Namespace: "namespace",
Port: 80,
Weight: 100,
},
},
{
name: "full k8s FQDN",
dest: &networking.HTTPRouteDestination{
Destination: &networking.Destination{
Host: "service.namespace.svc.cluster.local",
Port: &networking.PortSelector{
Number: 8080,
},
},
Weight: 50,
},
expected: model.BackendService{
Name: "service",
Namespace: "namespace",
Port: 8080,
Weight: 50,
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := ConvertBackendService(tc.dest)
assert.Equal(t, tc.expected.Name, result.Name)
assert.Equal(t, tc.expected.Namespace, result.Namespace)
assert.Equal(t, tc.expected.Port, result.Port)
assert.Equal(t, tc.expected.Weight, result.Weight)
})
}
}
func TestCreateConvertedName(t *testing.T) {
testCases := []struct {
name string
items []string
expected string
}{
{
name: "empty slice",
items: []string{},
expected: "",
},
{
name: "single item",
items: []string{"example"},
expected: "example",
},
{
name: "multiple items",
items: []string{"part1", "part2", "part3"},
expected: "part1-part2-part3",
},
{
name: "with empty strings",
items: []string{"part1", "", "part3"},
expected: "part1-part3",
},
{
name: "all empty strings",
items: []string{"", "", ""},
expected: "",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := CreateConvertedName(tc.items...)
assert.Equal(t, tc.expected, result)
})
}
}
func TestSortIngressByCreationTime(t *testing.T) {
configs := []config.Config{
{
Meta: config.Meta{
Name: "c-ingress",
Namespace: "ns1",
},
},
{
Meta: config.Meta{
Name: "a-ingress",
Namespace: "ns1",
},
},
{
Meta: config.Meta{
Name: "b-ingress",
Namespace: "ns1",
},
},
}
expected := []string{"a-ingress", "b-ingress", "c-ingress"}
SortIngressByCreationTime(configs)
var actual []string
for _, cfg := range configs {
actual = append(actual, cfg.Name)
}
assert.Equal(t, expected, actual, "When the timestamps are the same, the configuration should be sorted by name")
sameNamespaceConfigs := []config.Config{
{
Meta: config.Meta{
Name: "same-name",
Namespace: "c-ns",
},
},
{
Meta: config.Meta{
Name: "same-name",
Namespace: "a-ns",
},
},
{
Meta: config.Meta{
Name: "same-name",
Namespace: "b-ns",
},
},
}
expectedNamespace := []string{"a-ns", "b-ns", "c-ns"}
SortIngressByCreationTime(sameNamespaceConfigs)
var actualNamespace []string
for _, cfg := range sameNamespaceConfigs {
actualNamespace = append(actualNamespace, cfg.Namespace)
}
assert.Equal(t, expectedNamespace, actualNamespace, "When the names are the same, the configuration should be sorted by namespace")
}
func TestPartMd5(t *testing.T) {
testCases := []struct {
name string
input string
length int
}{
{
name: "empty string",
input: "",
length: 8,
},
{
name: "simple string",
input: "test",
length: 8,
},
{
name: "complex string",
input: "this-is-a-long-string-with-special-chars-!@#$%^&*()",
length: 8,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := partMd5(tc.input)
// Check result format
assert.Equal(t, tc.length, len(result), "MD5 hash excerpt should be 8 characters")
// Run twice to ensure deterministic output
result2 := partMd5(tc.input)
assert.Equal(t, result, result2, "partMd5 function should be deterministic")
})
}
}
func TestGetLbStatusListV1AndV1Beta1(t *testing.T) {
clusterPrefix = "gw-123-"
svcName := clusterPrefix
svcList := []*v1.Service{
{
ObjectMeta: metav1.ObjectMeta{
Name: svcName,
},
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeLoadBalancer,
},
Status: v1.ServiceStatus{
LoadBalancer: v1.LoadBalancerStatus{
Ingress: []v1.LoadBalancerIngress{
{
IP: "2.2.2.2",
},
},
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: svcName,
},
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeLoadBalancer,
},
Status: v1.ServiceStatus{
LoadBalancer: v1.LoadBalancerStatus{
Ingress: []v1.LoadBalancerIngress{
{
Hostname: "1.1.1.1" + SvcHostNameSuffix,
},
},
},
},
},
}
// Test the V1 version
t.Run("GetLbStatusListV1", func(t *testing.T) {
lbiList := GetLbStatusListV1(svcList)
assert.Equal(t, 2, len(lbiList), "There should be 2 entry points")
assert.Equal(t, "1.1.1.1", lbiList[0].IP, "The first IP should be 1.1.1.1")
assert.Equal(t, "2.2.2.2", lbiList[1].IP, "The second IP should be 2.2.2.2")
})
// Test the V1Beta1 version
t.Run("GetLbStatusListV1Beta1", func(t *testing.T) {
lbiList := GetLbStatusListV1Beta1(svcList)
assert.Equal(t, 2, len(lbiList), "There should be 2 entry points")
assert.Equal(t, "1.1.1.1", lbiList[0].IP, "The first IP should be 1.1.1.1")
assert.Equal(t, "2.2.2.2", lbiList[1].IP, "The second IP should be 2.2.2.2")
})
}

View File

@@ -18,6 +18,7 @@ import (
"reflect"
"sync/atomic"
"github.com/alibaba/higress/registry/reconcile"
"istio.io/istio/pilot/pkg/model"
"istio.io/istio/pkg/cluster"
"istio.io/istio/pkg/config"
@@ -58,6 +59,7 @@ type ItemController interface {
ValidHigressConfig(higressConfig *HigressConfig) error
ConstructEnvoyFilters() ([]*config.Config, error)
RegisterItemEventHandler(eventHandler ItemEventHandler)
RegisterMcpReconciler(reconciler *reconcile.Reconciler)
}
type ConfigmapMgr struct {
@@ -111,6 +113,12 @@ func (c *ConfigmapMgr) GetHigressConfig() *HigressConfig {
return nil
}
func (c *ConfigmapMgr) SetMcpReconciler(reconciler *reconcile.Reconciler) {
for _, itemController := range c.ItemControllers {
itemController.RegisterMcpReconciler(reconciler)
}
}
func (c *ConfigmapMgr) AddItemControllers(controllers ...ItemController) {
c.ItemControllers = append(c.ItemControllers, controllers...)
}

View File

@@ -21,6 +21,7 @@ import (
"github.com/alibaba/higress/pkg/ingress/kube/util"
. "github.com/alibaba/higress/pkg/ingress/log"
"github.com/alibaba/higress/registry/reconcile"
networking "istio.io/api/networking/v1alpha3"
"istio.io/istio/pkg/config"
"istio.io/istio/pkg/config/schema/gvk"
@@ -376,6 +377,9 @@ func (g *GlobalOptionController) RegisterItemEventHandler(eventHandler ItemEvent
g.eventHandler = eventHandler
}
func (g *GlobalOptionController) RegisterMcpReconciler(reconciler *reconcile.Reconciler) {
}
// generateDownstreamEnvoyFilter generates the downstream envoy filter.
func (g *GlobalOptionController) generateDownstreamEnvoyFilter(downstreamValueStruct string, bufferLimitStruct string, routeTimeoutStruct string, namespace string) []*networking.EnvoyFilter_EnvoyConfigObjectPatch {
var downstreamConfig []*networking.EnvoyFilter_EnvoyConfigObjectPatch

View File

@@ -23,6 +23,7 @@ import (
"github.com/alibaba/higress/pkg/ingress/kube/util"
. "github.com/alibaba/higress/pkg/ingress/log"
"github.com/alibaba/higress/registry/reconcile"
networking "istio.io/api/networking/v1alpha3"
"istio.io/istio/pkg/config"
"istio.io/istio/pkg/config/schema/gvk"
@@ -291,6 +292,9 @@ func (g *GzipController) RegisterItemEventHandler(eventHandler ItemEventHandler)
g.eventHandler = eventHandler
}
func (g *GzipController) RegisterMcpReconciler(reconciler *reconcile.Reconciler) {
}
func (g *GzipController) constructGzipStruct(gzip *Gzip, namespace string) string {
gzipConfig := ""
contentType := ""

View File

@@ -24,6 +24,7 @@ import (
"github.com/alibaba/higress/pkg/ingress/kube/util"
. "github.com/alibaba/higress/pkg/ingress/log"
"github.com/alibaba/higress/registry/reconcile"
networking "istio.io/api/networking/v1alpha3"
"istio.io/istio/pkg/config"
"istio.io/istio/pkg/config/schema/gvk"
@@ -61,6 +62,8 @@ type SSEServer struct {
Type string `json:"type,omitempty"`
// Additional Config parameters for the real MCP server implementation
Config map[string]interface{} `json:"config,omitempty"`
// The domain list of the SSE server
DomainList []string `json:"domain_list,omitempty"`
}
// MatchRule defines a rule for matching requests
@@ -179,9 +182,10 @@ func deepCopyMcpServer(mcp *McpServer) (*McpServer, error) {
newMcp.Servers = make([]*SSEServer, len(mcp.Servers))
for i, server := range mcp.Servers {
newServer := &SSEServer{
Name: server.Name,
Path: server.Path,
Type: server.Type,
Name: server.Name,
Path: server.Path,
Type: server.Type,
DomainList: server.DomainList,
}
if server.Config != nil {
newServer.Config = make(map[string]interface{})
@@ -212,6 +216,7 @@ type McpServerController struct {
mcpServer atomic.Value
Name string
eventHandler ItemEventHandler
reconclier *reconcile.Reconciler
}
func NewMcpServerController(namespace string) *McpServerController {
@@ -285,6 +290,10 @@ func (m *McpServerController) RegisterItemEventHandler(eventHandler ItemEventHan
m.eventHandler = eventHandler
}
func (m *McpServerController) RegisterMcpReconciler(reconciler *reconcile.Reconciler) {
m.reconclier = reconciler
}
func (m *McpServerController) ConstructEnvoyFilters() ([]*config.Config, error) {
configs := make([]*config.Config, 0)
mcpServer := m.GetMcpServer()
@@ -294,88 +303,122 @@ func (m *McpServerController) ConstructEnvoyFilters() ([]*config.Config, error)
return configs, nil
}
mcpStruct := m.constructMcpServerStruct(mcpServer)
if mcpStruct == "" {
return configs, nil
}
config := &config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.EnvoyFilter,
Name: higressMcpServerEnvoyFilterName,
Namespace: namespace,
},
Spec: &networking.EnvoyFilter{
ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
{
ApplyTo: networking.EnvoyFilter_HTTP_FILTER,
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Context: networking.EnvoyFilter_GATEWAY,
ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{
Listener: &networking.EnvoyFilter_ListenerMatch{
FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{
Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{
Name: "envoy.filters.network.http_connection_manager",
SubFilter: &networking.EnvoyFilter_ListenerMatch_SubFilterMatch{
Name: "envoy.filters.http.cors",
// mcp-session envoy filter
mcpSessionStruct := m.constructMcpSessionStruct(mcpServer)
if mcpSessionStruct != "" {
sessionConfig := &config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.EnvoyFilter,
Name: higressMcpServerEnvoyFilterName,
Namespace: namespace,
},
Spec: &networking.EnvoyFilter{
ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
{
ApplyTo: networking.EnvoyFilter_HTTP_FILTER,
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Context: networking.EnvoyFilter_GATEWAY,
ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{
Listener: &networking.EnvoyFilter_ListenerMatch{
FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{
Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{
Name: "envoy.filters.network.http_connection_manager",
SubFilter: &networking.EnvoyFilter_ListenerMatch_SubFilterMatch{
Name: "envoy.filters.http.cors",
},
},
},
},
},
},
},
Patch: &networking.EnvoyFilter_Patch{
Operation: networking.EnvoyFilter_Patch_INSERT_AFTER,
Value: util.BuildPatchStruct(mcpStruct),
Patch: &networking.EnvoyFilter_Patch{
Operation: networking.EnvoyFilter_Patch_INSERT_AFTER,
Value: util.BuildPatchStruct(mcpSessionStruct),
},
},
},
},
},
}
configs = append(configs, sessionConfig)
}
// mcp-server envoy filter
mcpServerStruct := m.constructMcpServerStruct(mcpServer)
if mcpServerStruct != "" {
serverConfig := &config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.EnvoyFilter,
Name: higressMcpServerEnvoyFilterName + "-server",
Namespace: namespace,
},
Spec: &networking.EnvoyFilter{
ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
{
ApplyTo: networking.EnvoyFilter_HTTP_FILTER,
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Context: networking.EnvoyFilter_GATEWAY,
ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{
Listener: &networking.EnvoyFilter_ListenerMatch{
FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{
Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{
Name: "envoy.filters.network.http_connection_manager",
SubFilter: &networking.EnvoyFilter_ListenerMatch_SubFilterMatch{
Name: "envoy.filters.http.router",
},
},
},
},
},
},
Patch: &networking.EnvoyFilter_Patch{
Operation: networking.EnvoyFilter_Patch_INSERT_BEFORE,
Value: util.BuildPatchStruct(mcpServerStruct),
},
},
},
},
}
configs = append(configs, serverConfig)
}
configs = append(configs, config)
return configs, nil
}
func (m *McpServerController) constructMcpServerStruct(mcp *McpServer) string {
// Build servers configuration
servers := "[]"
if len(mcp.Servers) > 0 {
serverConfigs := make([]string, len(mcp.Servers))
for i, server := range mcp.Servers {
serverConfig := fmt.Sprintf(`{
"name": "%s",
"path": "%s",
"type": "%s"`,
server.Name, server.Path, server.Type)
if len(server.Config) > 0 {
config, _ := json.Marshal(server.Config)
serverConfig += fmt.Sprintf(`,
"config": %s`, string(config))
}
serverConfig += "}"
serverConfigs[i] = serverConfig
}
servers = fmt.Sprintf("[%s]", strings.Join(serverConfigs, ","))
}
func (m *McpServerController) constructMcpSessionStruct(mcp *McpServer) string {
// Build match_list configuration
matchList := "[]"
var matchConfigs []string
if len(mcp.MatchList) > 0 {
matchConfigs := make([]string, len(mcp.MatchList))
for i, rule := range mcp.MatchList {
matchConfigs[i] = fmt.Sprintf(`{
for _, rule := range mcp.MatchList {
matchConfigs = append(matchConfigs, fmt.Sprintf(`{
"match_rule_domain": "%s",
"match_rule_path": "%s",
"match_rule_type": "%s"
}`, rule.MatchRuleDomain, rule.MatchRulePath, rule.MatchRuleType)
}`, rule.MatchRuleDomain, rule.MatchRulePath, rule.MatchRuleType))
}
matchList = fmt.Sprintf("[%s]", strings.Join(matchConfigs, ","))
}
// 构建 Redis 配置
if m.reconclier != nil {
vsFromMcp := m.reconclier.GetAllConfigs(gvk.VirtualService)
for _, c := range vsFromMcp {
vs := c.Spec.(*networking.VirtualService)
var host string
if len(vs.Hosts) > 1 {
host = fmt.Sprintf("(%s)", strings.Join(vs.Hosts, "|"))
} else {
host = vs.Hosts[0]
}
path := vs.Http[0].Match[0].Uri.GetPrefix()
matchConfigs = append(matchConfigs, fmt.Sprintf(`{
"match_rule_domain": "%s",
"match_rule_path": "%s",
"match_rule_type": "prefix"
}`, host, path))
}
}
matchList = fmt.Sprintf("[%s]", strings.Join(matchConfigs, ","))
// Build redis configuration
redisConfig := "null"
if mcp.Redis != nil {
redisConfig = fmt.Sprintf(`{
@@ -386,7 +429,7 @@ func (m *McpServerController) constructMcpServerStruct(mcp *McpServer) string {
}`, mcp.Redis.Address, mcp.Redis.Username, mcp.Redis.Password, mcp.Redis.DB)
}
// 构建限流配置
// Build rate limit configuration
rateLimitConfig := "null"
if mcp.Ratelimit != nil {
whiteList := "[]"
@@ -417,7 +460,6 @@ func (m *McpServerController) constructMcpServerStruct(mcp *McpServer) string {
"rate_limit": %s,
"sse_path_suffix": "%s",
"match_list": %s,
"servers": %s,
"enable_user_level_server": %t
}
}
@@ -428,6 +470,53 @@ func (m *McpServerController) constructMcpServerStruct(mcp *McpServer) string {
rateLimitConfig,
mcp.SsePathSuffix,
matchList,
servers,
mcp.EnableUserLevelServer)
}
func (m *McpServerController) constructMcpServerStruct(mcp *McpServer) string {
// Build servers configuration
servers := "[]"
if len(mcp.Servers) > 0 {
serverConfigs := make([]string, len(mcp.Servers))
for i, server := range mcp.Servers {
serverConfig := fmt.Sprintf(`{
"name": "%s",
"path": "%s",
"type": "%s"`,
server.Name, server.Path, server.Type)
if len(server.DomainList) > 0 {
domainList := fmt.Sprintf(`["%s"]`, strings.Join(server.DomainList, `","`))
serverConfig += fmt.Sprintf(`,
"domain_list": %s`, domainList)
}
if len(server.Config) > 0 {
config, _ := json.Marshal(server.Config)
serverConfig += fmt.Sprintf(`,
"config": %s`, string(config))
}
serverConfig += "}"
serverConfigs[i] = serverConfig
}
servers = fmt.Sprintf("[%s]", strings.Join(serverConfigs, ","))
}
// Build complete configuration structure
return fmt.Sprintf(`{
"name": "envoy.filters.http.golang",
"typed_config": {
"@type": "type.googleapis.com/udpa.type.v1.TypedStruct",
"type_url": "type.googleapis.com/envoy.extensions.filters.http.golang.v3alpha.Config",
"value": {
"library_id": "mcp-server",
"library_path": "/var/lib/istio/envoy/golang-filter.so",
"plugin_name": "mcp-server",
"plugin_config": {
"@type": "type.googleapis.com/xds.type.v3.TypedStruct",
"value": {
"servers": %s
}
}
}
}
}`, servers)
}

View File

@@ -15,6 +15,7 @@
package configmap
import (
"encoding/json"
"errors"
"testing"
@@ -422,3 +423,311 @@ func TestMcpServerController_AddOrUpdateHigressConfig(t *testing.T) {
})
}
}
func TestMcpServerController_ValidHigressConfig(t *testing.T) {
tests := []struct {
name string
higressConfig *HigressConfig
wantErr error
}{
{
name: "nil config",
higressConfig: nil,
wantErr: nil,
},
{
name: "nil mcp server",
higressConfig: &HigressConfig{
McpServer: nil,
},
wantErr: nil,
},
{
name: "valid config",
higressConfig: &HigressConfig{
McpServer: &McpServer{
Enable: true,
Redis: &RedisConfig{
Address: "localhost:6379",
},
MatchList: []*MatchRule{},
Servers: []*SSEServer{},
},
},
wantErr: nil,
},
{
name: "invalid config - user level server without redis",
higressConfig: &HigressConfig{
McpServer: &McpServer{
Enable: true,
EnableUserLevelServer: true,
Redis: nil,
MatchList: []*MatchRule{},
Servers: []*SSEServer{},
},
},
wantErr: errors.New("redis config cannot be empty when user level server is enabled"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m := NewMcpServerController("test-namespace")
err := m.ValidHigressConfig(tt.higressConfig)
assert.Equal(t, tt.wantErr, err)
})
}
}
func TestMcpServerController_ConstructEnvoyFilters(t *testing.T) {
tests := []struct {
name string
mcpServer *McpServer
wantConfigs int
wantErr error
}{
{
name: "nil mcp server",
mcpServer: nil,
wantConfigs: 0,
wantErr: nil,
},
{
name: "disabled mcp server",
mcpServer: &McpServer{
Enable: false,
},
wantConfigs: 0,
wantErr: nil,
},
{
name: "valid mcp server with redis",
mcpServer: &McpServer{
Enable: true,
Redis: &RedisConfig{
Address: "localhost:6379",
},
MatchList: []*MatchRule{},
Servers: []*SSEServer{},
},
wantConfigs: 2, // Both session and server filters
wantErr: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m := NewMcpServerController("test-namespace")
m.mcpServer.Store(tt.mcpServer)
configs, err := m.ConstructEnvoyFilters()
assert.Equal(t, tt.wantErr, err)
assert.Equal(t, tt.wantConfigs, len(configs))
})
}
}
func TestMcpServerController_constructMcpSessionStruct(t *testing.T) {
tests := []struct {
name string
mcp *McpServer
wantJSON string
}{
{
name: "minimal config",
mcp: &McpServer{
Enable: true,
Redis: &RedisConfig{
Address: "localhost:6379",
},
MatchList: []*MatchRule{},
Servers: []*SSEServer{},
},
wantJSON: `{
"name": "envoy.filters.http.golang",
"typed_config": {
"@type": "type.googleapis.com/udpa.type.v1.TypedStruct",
"type_url": "type.googleapis.com/envoy.extensions.filters.http.golang.v3alpha.Config",
"value": {
"library_id": "mcp-session",
"library_path": "/var/lib/istio/envoy/golang-filter.so",
"plugin_name": "mcp-session",
"plugin_config": {
"@type": "type.googleapis.com/xds.type.v3.TypedStruct",
"value": {
"redis": {
"address": "localhost:6379",
"username": "",
"password": "",
"db": 0
},
"rate_limit": null,
"sse_path_suffix": "",
"match_list": [],
"enable_user_level_server": false
}
}
}
}
}`,
},
{
name: "full config",
mcp: &McpServer{
Enable: true,
Redis: &RedisConfig{
Address: "localhost:6379",
Username: "user",
Password: "pass",
DB: 1,
},
SsePathSuffix: "/sse",
MatchList: []*MatchRule{
{
MatchRuleDomain: "*",
MatchRulePath: "/test",
MatchRuleType: "exact",
},
},
EnableUserLevelServer: true,
Ratelimit: &MCPRatelimitConfig{
Limit: 100,
Window: 3600,
WhiteList: []string{"user1", "user2"},
},
},
wantJSON: `{
"name": "envoy.filters.http.golang",
"typed_config": {
"@type": "type.googleapis.com/udpa.type.v1.TypedStruct",
"type_url": "type.googleapis.com/envoy.extensions.filters.http.golang.v3alpha.Config",
"value": {
"library_id": "mcp-session",
"library_path": "/var/lib/istio/envoy/golang-filter.so",
"plugin_name": "mcp-session",
"plugin_config": {
"@type": "type.googleapis.com/xds.type.v3.TypedStruct",
"value": {
"redis": {
"address": "localhost:6379",
"username": "user",
"password": "pass",
"db": 1
},
"rate_limit": {
"limit": 100,
"window": 3600,
"white_list": ["user1","user2"]
},
"sse_path_suffix": "/sse",
"match_list": [{
"match_rule_domain": "*",
"match_rule_path": "/test",
"match_rule_type": "exact"
}],
"enable_user_level_server": true
}
}
}
}
}`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m := NewMcpServerController("test-namespace")
got := m.constructMcpSessionStruct(tt.mcp)
// Normalize JSON strings for comparison
var gotJSON, wantJSON interface{}
json.Unmarshal([]byte(got), &gotJSON)
json.Unmarshal([]byte(tt.wantJSON), &wantJSON)
assert.Equal(t, wantJSON, gotJSON)
})
}
}
func TestMcpServerController_constructMcpServerStruct(t *testing.T) {
tests := []struct {
name string
mcp *McpServer
wantJSON string
}{
{
name: "no servers",
mcp: &McpServer{
Servers: []*SSEServer{},
},
wantJSON: `{
"name": "envoy.filters.http.golang",
"typed_config": {
"@type": "type.googleapis.com/udpa.type.v1.TypedStruct",
"type_url": "type.googleapis.com/envoy.extensions.filters.http.golang.v3alpha.Config",
"value": {
"library_id": "mcp-server",
"library_path": "/var/lib/istio/envoy/golang-filter.so",
"plugin_name": "mcp-server",
"plugin_config": {
"@type": "type.googleapis.com/xds.type.v3.TypedStruct",
"value": {
"servers": []
}
}
}
}
}`,
},
{
name: "with servers",
mcp: &McpServer{
Servers: []*SSEServer{
{
Name: "test-server",
Path: "/test",
Type: "test",
Config: map[string]interface{}{
"key": "value",
},
DomainList: []string{"example.com"},
},
},
},
wantJSON: `{
"name": "envoy.filters.http.golang",
"typed_config": {
"@type": "type.googleapis.com/udpa.type.v1.TypedStruct",
"type_url": "type.googleapis.com/envoy.extensions.filters.http.golang.v3alpha.Config",
"value": {
"library_id": "mcp-server",
"library_path": "/var/lib/istio/envoy/golang-filter.so",
"plugin_name": "mcp-server",
"plugin_config": {
"@type": "type.googleapis.com/xds.type.v3.TypedStruct",
"value": {
"servers": [{
"name": "test-server",
"path": "/test",
"type": "test",
"domain_list": ["example.com"],
"config": {"key":"value"}
}]
}
}
}
}
}`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m := NewMcpServerController("test-namespace")
got := m.constructMcpServerStruct(tt.mcp)
// Normalize JSON strings for comparison
var gotJSON, wantJSON interface{}
json.Unmarshal([]byte(got), &gotJSON)
json.Unmarshal([]byte(tt.wantJSON), &wantJSON)
assert.Equal(t, wantJSON, gotJSON)
})
}
}

View File

@@ -21,6 +21,7 @@ import (
"reflect"
"sync/atomic"
"github.com/alibaba/higress/registry/reconcile"
"istio.io/istio/pkg/config"
"istio.io/istio/pkg/config/schema/gvk"
@@ -237,6 +238,9 @@ func (t *TracingController) RegisterItemEventHandler(eventHandler ItemEventHandl
t.eventHandler = eventHandler
}
func (t *TracingController) RegisterMcpReconciler(reconciler *reconcile.Reconciler) {
}
func (t *TracingController) ConstructEnvoyFilters() ([]*config.Config, error) {
configs := make([]*config.Config, 0)
tracing := t.GetTracing()

View File

@@ -1,4 +1,4 @@
FROM golang:1.23-bullseye AS golang-base
FROM golang:1.22-bullseye AS golang-base
ARG GOPROXY
ARG GO_FILTER_NAME
@@ -24,7 +24,7 @@ WORKDIR /workspace
COPY . .
WORKDIR /workspace/$GO_FILTER_NAME
WORKDIR /workspace
RUN go mod tidy
RUN if [ "$GOARCH" = "arm64" ]; then \

View File

@@ -1,4 +1,4 @@
GO_FILTER_NAME ?= mcp-server
GO_FILTER_NAME ?= golang-filter
GOPROXY := $(shell go env GOPROXY)
GOARCH ?= amd64
@@ -8,5 +8,5 @@ build:
--build-arg GO_FILTER_NAME=${GO_FILTER_NAME} \
--build-arg GOARCH=${GOARCH} \
-t ${GO_FILTER_NAME} \
--output ./${GO_FILTER_NAME} \
--output . \
.

View File

@@ -28,7 +28,7 @@ http_filters:
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.golang.v3alpha.Config
library_id: my-go-filter
library_path: "./my-go-filter.so"
library_path: "./go-filter.so"
plugin_name: my-go-filter
plugin_config:
"@type": type.googleapis.com/xds.type.v3.TypedStruct
@@ -43,5 +43,5 @@ http_filters:
使用以下命令可以快速构建 golang filter 插件:
```bash
GO_FILTER_NAME=mcp-server make build
make build
```

View File

@@ -1,6 +1,10 @@
module github.com/alibaba/higress/plugins/golang-filter/mcp-server
module github.com/alibaba/higress/plugins/golang-filter
go 1.23
go 1.22
replace github.com/envoyproxy/envoy => github.com/higress-group/envoy v0.0.0-20250428030521-17cf01d9f644
replace github.com/mark3labs/mcp-go => github.com/higress-group/mcp-go v0.0.0-20250428145706-792ce64b4b30
require (
github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42

View File

@@ -136,8 +136,6 @@ github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9r
github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/envoyproxy/envoy v1.33.1-0.20250325161043-11ab50a29d99 h1:jih/Ieb7BFgVCStgvY5fXQ3mI9ByOt4wfwUF0d7qmqI=
github.com/envoyproxy/envoy v1.33.1-0.20250325161043-11ab50a29d99/go.mod h1:x7d0dNbE0xGuDBUkBg19VGCgnPQ+lJ2k8lDzDzKExow=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -236,6 +234,10 @@ github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mO
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/higress-group/envoy v0.0.0-20250428030521-17cf01d9f644 h1:wiLDdiOT3BcTQSFs8oTMu54GIiPFSwKLuWo5J0Cd9b8=
github.com/higress-group/envoy v0.0.0-20250428030521-17cf01d9f644/go.mod h1:SU+IJUAfh1kkZtH+u0E1dnwho8AhbGeYMgp5vvjU+Gc=
github.com/higress-group/mcp-go v0.0.0-20250428145706-792ce64b4b30 h1:N4NMq8M1nZyyChPyzn+EUUdHi5asig2uLR5hOyRmsXI=
github.com/higress-group/mcp-go v0.0.0-20250428145706-792ce64b4b30/go.mod h1:O9gri9UOzthw728vusc2oNu99lVh8cKCajpxNfC90gE=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
@@ -283,8 +285,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/luoxiner/nacos-sdk-go/v2 v2.2.9-40 h1:nzRTBplC0riQqQwEHZThw5H4/TH5LgWTQTm6A7t1lpY=
github.com/luoxiner/nacos-sdk-go/v2 v2.2.9-40/go.mod h1:9FKXl6FqOiVmm72i8kADtbeK71egyG9y3uRDBg41tpQ=
github.com/mark3labs/mcp-go v0.12.0 h1:Pue1Tdwqcz77GHq18uzgmLT3wmeDUxXUSAqSwhGLhVo=
github.com/mark3labs/mcp-go v0.12.0/go.mod h1:cjMlBU0cv/cj9kjlgmRhoJ5JREdS7YX83xeIG9Ko/jE=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=

View File

@@ -0,0 +1,25 @@
package main
import (
"net/http"
mcp_server "github.com/alibaba/higress/plugins/golang-filter/mcp-server"
mcp_session "github.com/alibaba/higress/plugins/golang-filter/mcp-session"
"github.com/envoyproxy/envoy/contrib/golang/common/go/api"
envoyHttp "github.com/envoyproxy/envoy/contrib/golang/filters/http/source/go/pkg/http"
)
func init() {
envoyHttp.RegisterHttpFilterFactoryAndConfigParser(mcp_session.Name, mcp_session.FilterFactory, &mcp_session.Parser{})
envoyHttp.RegisterHttpFilterFactoryAndConfigParser(mcp_server.Name, mcp_server.FilterFactory, &mcp_server.Parser{})
go func() {
defer func() {
if r := recover(); r != nil {
api.LogErrorf("PProf server recovered from panic: %v", r)
}
}()
api.LogError(http.ListenAndServe("localhost:6060", nil).Error())
}()
}
func main() {}

View File

@@ -1,64 +1,39 @@
package main
package mcp_server
import (
"fmt"
"net/http"
_ "net/http/pprof"
xds "github.com/cncf/xds/go/xds/type/v3"
"google.golang.org/protobuf/types/known/anypb"
"github.com/alibaba/higress/plugins/golang-filter/mcp-server/handler"
"github.com/alibaba/higress/plugins/golang-filter/mcp-server/internal"
_ "github.com/alibaba/higress/plugins/golang-filter/mcp-server/registry/nacos"
_ "github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/gorm"
mcp_session "github.com/alibaba/higress/plugins/golang-filter/mcp-session"
"github.com/alibaba/higress/plugins/golang-filter/mcp-session/common"
xds "github.com/cncf/xds/go/xds/type/v3"
"github.com/envoyproxy/envoy/contrib/golang/common/go/api"
envoyHttp "github.com/envoyproxy/envoy/contrib/golang/filters/http/source/go/pkg/http"
"google.golang.org/protobuf/types/known/anypb"
)
const Name = "mcp-session"
const Name = "mcp-server"
const Version = "1.0.0"
const DefaultServerName = "defaultServer"
const ConfigPathSuffix = "/config"
func init() {
envoyHttp.RegisterHttpFilterFactoryAndConfigParser(Name, filterFactory, &parser{})
go func() {
defer func() {
if r := recover(); r != nil {
api.LogErrorf("PProf server recovered from panic: %v", r)
}
}()
api.LogError(http.ListenAndServe("localhost:6060", nil).Error())
}()
type SSEServerWrapper struct {
BaseServer *common.SSEServer
DomainList []string
}
type config struct {
ssePathSuffix string
redisClient *internal.RedisClient
servers []*internal.SSEServer
defaultServer *internal.SSEServer
matchList []internal.MatchRule
enableUserLevelServer bool
rateLimitConfig *handler.MCPRatelimitConfig
servers []*SSEServerWrapper
}
func (c *config) Destroy() {
if c.redisClient != nil {
api.LogDebug("Closing Redis client")
c.redisClient.Close()
}
for _, server := range c.servers {
server.Close()
server.BaseServer.Close()
}
}
type parser struct {
type Parser struct {
}
// Parse the filter configuration
func (p *parser) Parse(any *anypb.Any, callbacks api.ConfigCallbackHandler) (interface{}, error) {
func (p *Parser) Parse(any *anypb.Any, callbacks api.ConfigCallbackHandler) (interface{}, error) {
configStruct := &xds.TypedStruct{}
if err := any.UnmarshalTo(configStruct); err != nil {
return nil, err
@@ -66,82 +41,9 @@ func (p *parser) Parse(any *anypb.Any, callbacks api.ConfigCallbackHandler) (int
v := configStruct.Value
conf := &config{
matchList: make([]internal.MatchRule, 0),
servers: make([]*internal.SSEServer, 0),
servers: make([]*SSEServerWrapper, 0),
}
// Parse match_list if exists
if matchList, ok := v.AsMap()["match_list"].([]interface{}); ok {
for _, item := range matchList {
if ruleMap, ok := item.(map[string]interface{}); ok {
rule := internal.MatchRule{}
if domain, ok := ruleMap["match_rule_domain"].(string); ok {
rule.MatchRuleDomain = domain
}
if path, ok := ruleMap["match_rule_path"].(string); ok {
rule.MatchRulePath = path
}
if ruleType, ok := ruleMap["match_rule_type"].(string); ok {
rule.MatchRuleType = internal.RuleType(ruleType)
}
conf.matchList = append(conf.matchList, rule)
}
}
}
// Redis configuration is optional
if redisConfigMap, ok := v.AsMap()["redis"].(map[string]interface{}); ok {
redisConfig, err := internal.ParseRedisConfig(redisConfigMap)
if err != nil {
return nil, fmt.Errorf("failed to parse redis config: %w", err)
}
redisClient, err := internal.NewRedisClient(redisConfig)
if err != nil {
return nil, fmt.Errorf("failed to initialize RedisClient: %w", err)
}
conf.redisClient = redisClient
api.LogDebug("Redis client initialized")
} else {
api.LogDebug("Redis configuration not provided, running without Redis")
}
enableUserLevelServer, ok := v.AsMap()["enable_user_level_server"].(bool)
if !ok {
enableUserLevelServer = false
if conf.redisClient == nil {
return nil, fmt.Errorf("redis configuration is not provided, enable_user_level_server is true")
}
}
conf.enableUserLevelServer = enableUserLevelServer
if rateLimit, ok := v.AsMap()["rate_limit"].(map[string]interface{}); ok {
rateLimitConfig := &handler.MCPRatelimitConfig{}
if limit, ok := rateLimit["limit"].(float64); ok {
rateLimitConfig.Limit = int(limit)
}
if window, ok := rateLimit["window"].(float64); ok {
rateLimitConfig.Window = int(window)
}
if whiteList, ok := rateLimit["white_list"].([]interface{}); ok {
for _, item := range whiteList {
if uid, ok := item.(string); ok {
rateLimitConfig.Whitelist = append(rateLimitConfig.Whitelist, uid)
}
}
}
if errorText, ok := rateLimit["error_text"].(string); ok {
rateLimitConfig.ErrorText = errorText
}
conf.rateLimitConfig = rateLimitConfig
}
ssePathSuffix, ok := v.AsMap()["sse_path_suffix"].(string)
if !ok || ssePathSuffix == "" {
return nil, fmt.Errorf("sse path suffix is not set or empty")
}
conf.ssePathSuffix = ssePathSuffix
serverConfigs, ok := v.AsMap()["servers"].([]interface{})
if !ok {
api.LogDebug("No servers are configured")
@@ -153,19 +55,33 @@ func (p *parser) Parse(any *anypb.Any, callbacks api.ConfigCallbackHandler) (int
if !ok {
return nil, fmt.Errorf("server config must be an object")
}
serverType, ok := serverConfigMap["type"].(string)
if !ok {
return nil, fmt.Errorf("server type is not set")
}
serverPath, ok := serverConfigMap["path"].(string)
if !ok {
return nil, fmt.Errorf("server %s path is not set", serverType)
}
serverDomainList := []string{}
if domainList, ok := serverConfigMap["domain_list"].([]interface{}); ok {
for _, domain := range domainList {
if domainStr, ok := domain.(string); ok {
serverDomainList = append(serverDomainList, domainStr)
}
}
} else {
serverDomainList = []string{"*"}
}
serverName, ok := serverConfigMap["name"].(string)
if !ok {
return nil, fmt.Errorf("server %s name is not set", serverType)
}
server := internal.GlobalRegistry.GetServer(serverType)
server := common.GlobalRegistry.GetServer(serverType)
if server == nil {
return nil, fmt.Errorf("server %s is not registered", serverType)
@@ -186,50 +102,37 @@ func (p *parser) Parse(any *anypb.Any, callbacks api.ConfigCallbackHandler) (int
return nil, fmt.Errorf("failed to initialize DBServer: %w", err)
}
conf.servers = append(conf.servers, internal.NewSSEServer(serverInstance,
internal.WithRedisClient(conf.redisClient),
internal.WithSSEEndpoint(fmt.Sprintf("%s%s", serverPath, ssePathSuffix)),
internal.WithMessageEndpoint(serverPath)))
conf.servers = append(conf.servers, &SSEServerWrapper{
BaseServer: common.NewSSEServer(serverInstance,
common.WithRedisClient(common.GlobalRedisClient),
common.WithSSEEndpoint(fmt.Sprintf("%s%s", serverPath, mcp_session.GlobalSSEPathSuffix)),
common.WithMessageEndpoint(serverPath)),
DomainList: serverDomainList,
})
api.LogDebug(fmt.Sprintf("Registered MCP Server: %s", serverType))
}
return conf, nil
}
func (p *parser) Merge(parent interface{}, child interface{}) interface{} {
func (p *Parser) Merge(parent interface{}, child interface{}) interface{} {
parentConfig := parent.(*config)
childConfig := child.(*config)
newConfig := *parentConfig
if childConfig.redisClient != nil {
newConfig.redisClient = childConfig.redisClient
}
if childConfig.ssePathSuffix != "" {
newConfig.ssePathSuffix = childConfig.ssePathSuffix
}
if childConfig.servers != nil {
newConfig.servers = childConfig.servers
}
if childConfig.defaultServer != nil {
newConfig.defaultServer = childConfig.defaultServer
}
if childConfig.matchList != nil {
newConfig.matchList = childConfig.matchList
}
return &newConfig
}
func filterFactory(c interface{}, callbacks api.FilterCallbackHandler) api.StreamFilter {
func FilterFactory(c interface{}, callbacks api.FilterCallbackHandler) api.StreamFilter {
conf, ok := c.(*config)
if !ok {
panic("unexpected config type")
}
return &filter{
callbacks: callbacks,
config: conf,
stopChan: make(chan struct{}),
mcpConfigHandler: handler.NewMCPConfigHandler(conf.redisClient, callbacks),
mcpRatelimitHandler: handler.NewMCPRatelimitHandler(conf.redisClient, callbacks, conf.rateLimitConfig),
config: conf,
callbacks: callbacks,
}
}
func main() {}

View File

@@ -1,104 +1,44 @@
package main
package mcp_server
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"strconv"
"strings"
"github.com/alibaba/higress/plugins/golang-filter/mcp-server/handler"
"github.com/alibaba/higress/plugins/golang-filter/mcp-server/internal"
"github.com/alibaba/higress/plugins/golang-filter/mcp-session/common"
"github.com/envoyproxy/envoy/contrib/golang/common/go/api"
"github.com/mark3labs/mcp-go/mcp"
)
const (
RedisNotEnabledResponseBody = "Redis is not enabled, SSE connection is not supported"
)
// The callbacks in the filter, like `DecodeHeaders`, can be implemented on demand.
// Because api.PassThroughStreamFilter provides a default implementation.
type filter struct {
api.PassThroughStreamFilter
callbacks api.FilterCallbackHandler
path string
config *config
stopChan chan struct{}
req *http.Request
serverName string
message bool
proxyURL *url.URL
skip bool
userLevelConfig bool
mcpConfigHandler *handler.MCPConfigHandler
ratelimit bool
mcpRatelimitHandler *handler.MCPRatelimitHandler
config *config
req *http.Request
message bool
path string
}
type RequestURL struct {
method string
scheme string
host string
path string
baseURL string
parsedURL *url.URL
internalIP bool
}
func NewRequestURL(header api.RequestHeaderMap) *RequestURL {
method, _ := header.Get(":method")
scheme, _ := header.Get(":scheme")
host, _ := header.Get(":authority")
path, _ := header.Get(":path")
internalIP, _ := header.Get("x-envoy-internal")
baseURL := fmt.Sprintf("%s://%s", scheme, host)
parsedURL, _ := url.Parse(path)
api.LogDebugf("RequestURL: method=%s, scheme=%s, host=%s, path=%s", method, scheme, host, path)
return &RequestURL{method: method, scheme: scheme, host: host, path: path, baseURL: baseURL, parsedURL: parsedURL, internalIP: internalIP == "true"}
}
// Callbacks which are called in request path
// The endStream is true if the request doesn't have body
func (f *filter) DecodeHeaders(header api.RequestHeaderMap, endStream bool) api.StatusType {
url := NewRequestURL(header)
f.path = url.parsedURL.Path
// Check if request matches any rule in match_list
if !internal.IsMatch(f.config.matchList, url.host, f.path) {
f.skip = true
api.LogDebugf("Request does not match any rule in match_list: %s", url.parsedURL.String())
url := common.NewRequestURL(header)
if url == nil {
return api.Continue
}
f.path = url.ParsedURL.Path
for _, server := range f.config.servers {
if f.path == server.GetSSEEndpoint() {
if url.method != http.MethodGet {
f.callbacks.DecoderFilterCallbacks().SendLocalReply(http.StatusMethodNotAllowed, "Method not allowed", nil, 0, "")
} else {
f.serverName = server.GetServerName()
body := "SSE connection create"
f.callbacks.DecoderFilterCallbacks().SendLocalReply(http.StatusOK, body, nil, 0, "")
}
api.LogDebugf("%s SSE connection started", server.GetServerName())
return api.LocalReply
} else if f.path == server.GetMessageEndpoint() {
if url.method != http.MethodPost {
if common.MatchDomainList(url.ParsedURL.Host, server.DomainList) && url.ParsedURL.Path == server.BaseServer.GetMessageEndpoint() {
if url.Method != http.MethodPost {
f.callbacks.DecoderFilterCallbacks().SendLocalReply(http.StatusMethodNotAllowed, "Method not allowed", nil, 0, "")
return api.LocalReply
}
// Create a new http.Request object
f.req = &http.Request{
Method: url.method,
URL: url.parsedURL,
Method: url.Method,
URL: url.ParsedURL,
Header: make(http.Header),
}
api.LogDebugf("Message request: %v", url.parsedURL)
api.LogDebugf("Message request: %v", url.ParsedURL)
// Copy headers from api.RequestHeaderMap to http.Header
header.Range(func(key, value string) bool {
f.req.Header.Add(key, value)
@@ -113,209 +53,33 @@ func (f *filter) DecodeHeaders(header api.RequestHeaderMap, endStream bool) api.
}
}
f.req = &http.Request{
Method: url.method,
URL: url.parsedURL,
}
if strings.HasSuffix(f.path, ConfigPathSuffix) && f.config.enableUserLevelServer {
if !url.internalIP {
api.LogWarnf("Access denied: non-internal IP address %s", url.parsedURL.String())
f.callbacks.DecoderFilterCallbacks().SendLocalReply(http.StatusForbidden, "", nil, 0, "")
return api.LocalReply
}
if strings.HasSuffix(f.path, ConfigPathSuffix) && url.method == http.MethodGet {
api.LogDebugf("Handling config request: %s", f.path)
f.mcpConfigHandler.HandleConfigRequest(f.req, []byte{})
return api.LocalReply
}
f.userLevelConfig = true
if endStream {
return api.Continue
} else {
return api.StopAndBuffer
}
}
if !strings.HasSuffix(url.parsedURL.Path, f.config.ssePathSuffix) {
f.proxyURL = url.parsedURL
if f.config.enableUserLevelServer {
parts := strings.Split(url.parsedURL.Path, "/")
if len(parts) >= 3 {
serverName := parts[1]
uid := parts[2]
// Get encoded config
encodedConfig, _ := f.mcpConfigHandler.GetEncodedConfig(serverName, uid)
if encodedConfig != "" {
header.Set("x-higress-mcpserver-config", encodedConfig)
api.LogDebugf("Set x-higress-mcpserver-config Header for %s:%s", serverName, uid)
}
}
f.ratelimit = true
}
if endStream {
return api.Continue
} else {
return api.StopAndBuffer
}
}
if url.method != http.MethodGet {
f.callbacks.DecoderFilterCallbacks().SendLocalReply(http.StatusMethodNotAllowed, "Method not allowed", nil, 0, "")
} else {
f.config.defaultServer = internal.NewSSEServer(internal.NewMCPServer(DefaultServerName, Version),
internal.WithSSEEndpoint(f.config.ssePathSuffix),
internal.WithMessageEndpoint(strings.TrimSuffix(url.parsedURL.Path, f.config.ssePathSuffix)),
internal.WithRedisClient(f.config.redisClient))
f.serverName = f.config.defaultServer.GetServerName()
body := "SSE connection create"
f.callbacks.DecoderFilterCallbacks().SendLocalReply(http.StatusOK, body, nil, 0, "")
}
return api.LocalReply
return api.Continue
}
// DecodeData might be called multiple times during handling the request body.
// The endStream is true when handling the last piece of the body.
func (f *filter) DecodeData(buffer api.BufferInstance, endStream bool) api.StatusType {
if f.skip {
return api.Continue
}
if !endStream {
return api.StopAndBuffer
}
if f.message {
for _, server := range f.config.servers {
if f.path == server.GetMessageEndpoint() {
if f.path == server.BaseServer.GetMessageEndpoint() {
// Create a response recorder to capture the response
recorder := httptest.NewRecorder()
// Call the handleMessage method of SSEServer with complete body
httpStatus := server.HandleMessage(recorder, f.req, buffer.Bytes())
httpStatus := server.BaseServer.HandleMessage(recorder, f.req, buffer.Bytes())
f.message = false
f.callbacks.DecoderFilterCallbacks().SendLocalReply(httpStatus, recorder.Body.String(), recorder.Header(), 0, "")
return api.LocalReply
}
}
} else if f.userLevelConfig {
// Handle config POST request
api.LogDebugf("Handling config request: %s", f.path)
f.mcpConfigHandler.HandleConfigRequest(f.req, buffer.Bytes())
return api.LocalReply
} else if f.ratelimit {
if checkJSONRPCMethod(buffer.Bytes(), "tools/list") {
api.LogDebugf("Not a tools call request, skipping ratelimit")
return api.Continue
}
parts := strings.Split(f.req.URL.Path, "/")
if len(parts) < 3 {
api.LogWarnf("Access denied: no valid uid found")
f.callbacks.DecoderFilterCallbacks().SendLocalReply(http.StatusForbidden, "", nil, 0, "")
return api.LocalReply
}
serverName := parts[1]
uid := parts[2]
encodedConfig, err := f.mcpConfigHandler.GetEncodedConfig(serverName, uid)
if err != nil {
api.LogWarnf("Access denied: no valid config found for uid %s", uid)
f.callbacks.DecoderFilterCallbacks().SendLocalReply(http.StatusForbidden, "", nil, 0, "")
return api.LocalReply
} else if encodedConfig == "" && checkJSONRPCMethod(buffer.Bytes(), "tools/call") {
api.LogDebugf("Empty config found for %s:%s", serverName, uid)
if !f.mcpRatelimitHandler.HandleRatelimit(f.req, buffer.Bytes()) {
return api.LocalReply
}
}
}
return api.Continue
}
// Callbacks which are called in response path
// The endStream is true if the response doesn't have body
func (f *filter) EncodeHeaders(header api.ResponseHeaderMap, endStream bool) api.StatusType {
if f.skip {
return api.Continue
}
if f.serverName != "" {
if f.config.redisClient != nil {
header.Set("Content-Type", "text/event-stream")
header.Set("Cache-Control", "no-cache")
header.Set("Connection", "keep-alive")
header.Set("Access-Control-Allow-Origin", "*")
header.Del("Content-Length")
} else {
header.Set("Content-Length", strconv.Itoa(len(RedisNotEnabledResponseBody)))
}
return api.Continue
}
return api.Continue
}
// EncodeData might be called multiple times during handling the response body.
// The endStream is true when handling the last piece of the body.
func (f *filter) EncodeData(buffer api.BufferInstance, endStream bool) api.StatusType {
if f.skip {
return api.Continue
}
if !endStream {
return api.StopAndBuffer
}
if f.proxyURL != nil && f.config.redisClient != nil {
sessionID := f.proxyURL.Query().Get("sessionId")
if sessionID != "" {
channel := internal.GetSSEChannelName(sessionID)
eventData := fmt.Sprintf("event: message\ndata: %s\n\n", buffer.String())
publishErr := f.config.redisClient.Publish(channel, eventData)
if publishErr != nil {
api.LogErrorf("Failed to publish wasm mcp server message to Redis: %v", publishErr)
}
}
}
if f.serverName != "" {
if f.config.redisClient != nil {
// handle specific server
for _, server := range f.config.servers {
if f.serverName == server.GetServerName() {
buffer.Reset()
server.HandleSSE(f.callbacks, f.stopChan)
return api.Running
}
}
// handle default server
if f.serverName == f.config.defaultServer.GetServerName() {
buffer.Reset()
f.config.defaultServer.HandleSSE(f.callbacks, f.stopChan)
return api.Running
}
return api.Continue
} else {
buffer.SetString(RedisNotEnabledResponseBody)
return api.Continue
}
}
return api.Continue
}
// OnDestroy stops the goroutine
func (f *filter) OnDestroy(reason api.DestroyReason) {
api.LogDebugf("OnDestroy: reason=%v", reason)
if f.serverName != "" && f.stopChan != nil {
select {
case <-f.stopChan:
return
default:
api.LogDebug("Stopping SSE connection")
close(f.stopChan)
}
}
}
// check if the request is a tools/call request
func checkJSONRPCMethod(body []byte, method string) bool {
var request mcp.CallToolRequest
if err := json.Unmarshal(body, &request); err != nil {
api.LogWarnf("Failed to unmarshal request body: %v, not a JSON RPC request", err)
return true
}
return request.Method == method
}

View File

@@ -5,8 +5,8 @@ import (
"fmt"
"time"
"github.com/alibaba/higress/plugins/golang-filter/mcp-server/internal"
"github.com/alibaba/higress/plugins/golang-filter/mcp-server/registry"
"github.com/alibaba/higress/plugins/golang-filter/mcp-session/common"
"github.com/envoyproxy/envoy/contrib/golang/common/go/api"
"github.com/mark3labs/mcp-go/mcp"
"github.com/nacos-group/nacos-sdk-go/v2/clients"
@@ -15,7 +15,7 @@ import (
)
func init() {
internal.GlobalRegistry.RegisterServer("nacos-mcp-registry", &NacosConfig{})
common.GlobalRegistry.RegisterServer("nacos-mcp-registry", &NacosConfig{})
}
type NacosConfig struct {
@@ -28,7 +28,7 @@ type NacosConfig struct {
}
type McpServerToolsChangeListener struct {
mcpServer *internal.MCPServer
mcpServer *common.MCPServer
}
func (l *McpServerToolsChangeListener) OnToolChanged(reg registry.McpServerRegistry) {
@@ -137,8 +137,8 @@ func (c *NacosConfig) ParseConfig(config map[string]any) error {
return nil
}
func (c *NacosConfig) NewServer(serverName string) (*internal.MCPServer, error) {
mcpServer := internal.NewMCPServer(
func (c *NacosConfig) NewServer(serverName string) (*common.MCPServer, error) {
mcpServer := common.NewMCPServer(
serverName,
"1.0.0",
)
@@ -170,11 +170,11 @@ func (c *NacosConfig) NewServer(serverName string) (*internal.MCPServer, error)
return mcpServer, nil
}
func resetToolsToMcpServer(mcpServer *internal.MCPServer, reg registry.McpServerRegistry) {
wrappedTools := []internal.ServerTool{}
func resetToolsToMcpServer(mcpServer *common.MCPServer, reg registry.McpServerRegistry) {
wrappedTools := []common.ServerTool{}
tools := reg.ListToolsDesciption()
for _, tool := range tools {
wrappedTools = append(wrappedTools, internal.ServerTool{
wrappedTools = append(wrappedTools, common.ServerTool{
Tool: mcp.NewToolWithRawSchema(tool.Name, tool.Description, tool.InputSchema),
Handler: registry.HandleRegistryToolsCall(reg),
})

View File

@@ -9,7 +9,7 @@ import (
"net/url"
"strings"
"github.com/alibaba/higress/plugins/golang-filter/mcp-server/internal"
"github.com/alibaba/higress/plugins/golang-filter/mcp-session/common"
"github.com/mark3labs/mcp-go/mcp"
)
@@ -204,7 +204,7 @@ func CommonRemoteCall(reg McpServerRegistry, toolName string, parameters map[str
return remoteHandle.HandleToolCall(ctx, parameters)
}
func HandleRegistryToolsCall(reg McpServerRegistry) internal.ToolHandlerFunc {
func HandleRegistryToolsCall(reg McpServerRegistry) common.ToolHandlerFunc {
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
arguments := request.Params.Arguments
return CommonRemoteCall(reg, request.Params.Name, arguments)

View File

@@ -4,7 +4,7 @@ import (
"errors"
"fmt"
"github.com/alibaba/higress/plugins/golang-filter/mcp-server/internal"
"github.com/alibaba/higress/plugins/golang-filter/mcp-session/common"
"github.com/envoyproxy/envoy/contrib/golang/common/go/api"
"github.com/mark3labs/mcp-go/mcp"
)
@@ -12,7 +12,7 @@ import (
const Version = "1.0.0"
func init() {
internal.GlobalRegistry.RegisterServer("database", &DBConfig{})
common.GlobalRegistry.RegisterServer("database", &DBConfig{})
}
type DBConfig struct {
@@ -41,11 +41,11 @@ func (c *DBConfig) ParseConfig(config map[string]any) error {
return nil
}
func (c *DBConfig) NewServer(serverName string) (*internal.MCPServer, error) {
mcpServer := internal.NewMCPServer(
func (c *DBConfig) NewServer(serverName string) (*common.MCPServer, error) {
mcpServer := common.NewMCPServer(
serverName,
Version,
internal.WithInstructions(fmt.Sprintf("This is a %s database server", c.dbType)),
common.WithInstructions(fmt.Sprintf("This is a %s database server", c.dbType)),
)
dbClient := NewDBClient(c.dsn, c.dbType, mcpServer.GetDestoryChannel())

View File

@@ -5,12 +5,12 @@ import (
"encoding/json"
"fmt"
"github.com/alibaba/higress/plugins/golang-filter/mcp-server/internal"
"github.com/alibaba/higress/plugins/golang-filter/mcp-session/common"
"github.com/mark3labs/mcp-go/mcp"
)
// HandleQueryTool handles SQL query execution
func HandleQueryTool(dbClient *DBClient) internal.ToolHandlerFunc {
func HandleQueryTool(dbClient *DBClient) common.ToolHandlerFunc {
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
arguments := request.Params.Arguments
message, ok := arguments["sql"].(string)

View File

@@ -1,4 +1,4 @@
package internal
package common
import (
"crypto/aes"

View File

@@ -1,4 +1,4 @@
package internal
package common
import (
"regexp"
@@ -23,6 +23,27 @@ type MatchRule struct {
MatchRuleType RuleType `json:"match_rule_type"` // Type of match rule
}
// ParseMatchList parses the match list from the config
func ParseMatchList(matchListConfig []interface{}) []MatchRule {
matchList := make([]MatchRule, 0)
for _, item := range matchListConfig {
if ruleMap, ok := item.(map[string]interface{}); ok {
rule := MatchRule{}
if domain, ok := ruleMap["match_rule_domain"].(string); ok {
rule.MatchRuleDomain = domain
}
if path, ok := ruleMap["match_rule_path"].(string); ok {
rule.MatchRulePath = path
}
if ruleType, ok := ruleMap["match_rule_type"].(string); ok {
rule.MatchRuleType = RuleType(ruleType)
}
matchList = append(matchList, rule)
}
}
return matchList
}
// convertWildcardToRegex converts wildcard pattern to regex pattern
func convertWildcardToRegex(pattern string) string {
pattern = regexp.QuoteMeta(pattern)
@@ -87,3 +108,13 @@ func IsMatch(rules []MatchRule, host, path string) bool {
}
return false
}
// MatchDomainList checks if the domain matches any of the domains in the list
func MatchDomainList(domain string, domainList []string) bool {
for _, d := range domainList {
if matchDomain(domain, d) {
return true
}
}
return false
}

View File

@@ -1,4 +1,4 @@
package internal
package common
import (
"context"
@@ -9,6 +9,8 @@ import (
"github.com/go-redis/redis/v8"
)
var GlobalRedisClient *RedisClient
type RedisConfig struct {
address string
username string
@@ -249,6 +251,18 @@ func (r *RedisClient) Get(key string) (string, error) {
return value, nil
}
// Expire sets the expiration time for a key
func (r *RedisClient) Expire(key string, expiration time.Duration) error {
ok, err := r.client.Expire(r.ctx, key, expiration).Result()
if err != nil {
return fmt.Errorf("failed to set expiration for key: %w", err)
}
if !ok {
return fmt.Errorf("key does not exist")
}
return nil
}
// Close closes the Redis client and stops the keepalive goroutine
func (r *RedisClient) Close() error {
r.cancel()

View File

@@ -1,4 +1,4 @@
package internal
package common
var GlobalRegistry = NewServerRegistry()

View File

@@ -1,4 +1,4 @@
package internal
package common
import (
"context"
@@ -243,6 +243,7 @@ func (s *MCPServer) HandleMessage(
message json.RawMessage,
) mcp.JSONRPCMessage {
// Add server to context
ctx = context.WithValue(ctx, serverKey{}, s)
var baseMessage struct {

View File

@@ -1,4 +1,4 @@
package internal
package common
import (
"encoding/json"
@@ -210,15 +210,7 @@ func (s *SSEServer) HandleMessage(w http.ResponseWriter, r *http.Request, body j
var status int
// Only send response if there is one (not for notifications)
if response != nil {
eventData, _ := json.Marshal(response)
if sessionID != "" && s.redisClient != nil {
channel := GetSSEChannelName(sessionID)
publishErr := s.redisClient.Publish(channel, fmt.Sprintf("event: message\ndata: %s\n\n", eventData))
if publishErr != nil {
api.LogErrorf("Failed to publish message to Redis: %v", publishErr)
}
w.WriteHeader(http.StatusAccepted)
status = http.StatusAccepted
} else {

View File

@@ -0,0 +1,34 @@
package common
import (
"fmt"
"net/url"
"github.com/envoyproxy/envoy/contrib/golang/common/go/api"
)
type RequestURL struct {
Method string
Scheme string
Host string
Path string
BaseURL string
ParsedURL *url.URL
InternalIP bool
}
func NewRequestURL(header api.RequestHeaderMap) *RequestURL {
method, _ := header.Get(":method")
scheme, _ := header.Get(":scheme")
host, _ := header.Get(":authority")
path, _ := header.Get(":path")
internalIP, _ := header.Get("x-envoy-internal")
baseURL := fmt.Sprintf("%s://%s", scheme, host)
parsedURL, err := url.Parse(path)
if err != nil {
api.LogWarnf("url parse path:%s failed:%s", path, err)
return nil
}
api.LogDebugf("RequestURL: method=%s, scheme=%s, host=%s, path=%s", method, scheme, host, path)
return &RequestURL{Method: method, Scheme: scheme, Host: host, Path: path, BaseURL: baseURL, ParsedURL: parsedURL, InternalIP: internalIP == "true"}
}

View File

@@ -0,0 +1,143 @@
package mcp_session
import (
"fmt"
_ "net/http/pprof"
xds "github.com/cncf/xds/go/xds/type/v3"
"google.golang.org/protobuf/types/known/anypb"
"github.com/alibaba/higress/plugins/golang-filter/mcp-session/common"
"github.com/alibaba/higress/plugins/golang-filter/mcp-session/handler"
"github.com/envoyproxy/envoy/contrib/golang/common/go/api"
)
const Name = "mcp-session"
const Version = "1.0.0"
const ConfigPathSuffix = "/config"
const DefaultServerName = "higress-mcp-server"
var GlobalSSEPathSuffix = "/sse"
type config struct {
matchList []common.MatchRule
enableUserLevelServer bool
rateLimitConfig *handler.MCPRatelimitConfig
defaultServer *common.SSEServer
}
func (c *config) Destroy() {
if common.GlobalRedisClient != nil {
api.LogDebug("Closing Redis client")
common.GlobalRedisClient.Close()
}
}
type Parser struct {
}
// Parse the filter configuration
func (p *Parser) Parse(any *anypb.Any, callbacks api.ConfigCallbackHandler) (interface{}, error) {
configStruct := &xds.TypedStruct{}
if err := any.UnmarshalTo(configStruct); err != nil {
return nil, err
}
v := configStruct.Value
conf := &config{
matchList: make([]common.MatchRule, 0),
}
// Parse match_list if exists
if matchList, ok := v.AsMap()["match_list"].([]interface{}); ok {
conf.matchList = common.ParseMatchList(matchList)
}
// Redis configuration is optional
if redisConfigMap, ok := v.AsMap()["redis"].(map[string]interface{}); ok {
redisConfig, err := common.ParseRedisConfig(redisConfigMap)
if err != nil {
return nil, fmt.Errorf("failed to parse redis config: %w", err)
}
redisClient, err := common.NewRedisClient(redisConfig)
if err != nil {
return nil, fmt.Errorf("failed to initialize RedisClient: %w", err)
}
common.GlobalRedisClient = redisClient
api.LogDebug("Redis client initialized")
} else {
api.LogDebug("Redis configuration not provided, running without Redis")
}
enableUserLevelServer, ok := v.AsMap()["enable_user_level_server"].(bool)
if !ok {
enableUserLevelServer = false
if common.GlobalRedisClient == nil {
return nil, fmt.Errorf("redis configuration is not provided, enable_user_level_server is true")
}
}
conf.enableUserLevelServer = enableUserLevelServer
if rateLimit, ok := v.AsMap()["rate_limit"].(map[string]interface{}); ok {
rateLimitConfig := &handler.MCPRatelimitConfig{}
if limit, ok := rateLimit["limit"].(float64); ok {
rateLimitConfig.Limit = int(limit)
}
if window, ok := rateLimit["window"].(float64); ok {
rateLimitConfig.Window = int(window)
}
if whiteList, ok := rateLimit["white_list"].([]interface{}); ok {
for _, item := range whiteList {
if uid, ok := item.(string); ok {
rateLimitConfig.Whitelist = append(rateLimitConfig.Whitelist, uid)
}
}
}
if errorText, ok := rateLimit["error_text"].(string); ok {
rateLimitConfig.ErrorText = errorText
}
conf.rateLimitConfig = rateLimitConfig
}
ssePathSuffix, ok := v.AsMap()["sse_path_suffix"].(string)
if !ok || ssePathSuffix == "" {
return nil, fmt.Errorf("sse path suffix is not set or empty")
}
GlobalSSEPathSuffix = ssePathSuffix
return conf, nil
}
func (p *Parser) Merge(parent interface{}, child interface{}) interface{} {
parentConfig := parent.(*config)
childConfig := child.(*config)
newConfig := *parentConfig
if childConfig.matchList != nil {
newConfig.matchList = childConfig.matchList
}
newConfig.enableUserLevelServer = childConfig.enableUserLevelServer
if childConfig.rateLimitConfig != nil {
newConfig.rateLimitConfig = childConfig.rateLimitConfig
}
if childConfig.defaultServer != nil {
newConfig.defaultServer = childConfig.defaultServer
}
return &newConfig
}
func FilterFactory(c interface{}, callbacks api.FilterCallbackHandler) api.StreamFilter {
conf, ok := c.(*config)
if !ok {
panic("unexpected config type")
}
return &filter{
callbacks: callbacks,
config: conf,
stopChan: make(chan struct{}),
mcpConfigHandler: handler.NewMCPConfigHandler(common.GlobalRedisClient, callbacks),
mcpRatelimitHandler: handler.NewMCPRatelimitHandler(common.GlobalRedisClient, callbacks, conf.rateLimitConfig),
}
}

View File

@@ -0,0 +1,240 @@
package mcp_session
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"github.com/alibaba/higress/plugins/golang-filter/mcp-session/common"
"github.com/alibaba/higress/plugins/golang-filter/mcp-session/handler"
"github.com/envoyproxy/envoy/contrib/golang/common/go/api"
"github.com/mark3labs/mcp-go/mcp"
)
const (
RedisNotEnabledResponseBody = "Redis is not enabled, SSE connection is not supported"
)
// The callbacks in the filter, like `DecodeHeaders`, can be implemented on demand.
// Because api.PassThroughStreamFilter provides a default implementation.
type filter struct {
api.PassThroughStreamFilter
callbacks api.FilterCallbackHandler
path string
config *config
stopChan chan struct{}
req *http.Request
serverName string
proxyURL *url.URL
neepProcess bool
userLevelConfig bool
mcpConfigHandler *handler.MCPConfigHandler
ratelimit bool
mcpRatelimitHandler *handler.MCPRatelimitHandler
}
// Callbacks which are called in request path
// The endStream is true if the request doesn't have body
func (f *filter) DecodeHeaders(header api.RequestHeaderMap, endStream bool) api.StatusType {
url := common.NewRequestURL(header)
if url == nil {
return api.Continue
}
f.path = url.ParsedURL.Path
// Check if request matches any rule in match_list
if !common.IsMatch(f.config.matchList, url.Host, f.path) {
api.LogDebugf("Request does not match any rule in match_list: %s", url.ParsedURL.String())
return api.Continue
}
f.neepProcess = true
f.req = &http.Request{
Method: url.Method,
URL: url.ParsedURL,
}
if strings.HasSuffix(f.path, ConfigPathSuffix) && f.config.enableUserLevelServer {
if !url.InternalIP {
api.LogWarnf("Access denied: non-Internal IP address %s", url.ParsedURL.String())
f.callbacks.DecoderFilterCallbacks().SendLocalReply(http.StatusForbidden, "", nil, 0, "")
return api.LocalReply
}
if strings.HasSuffix(f.path, ConfigPathSuffix) && url.Method == http.MethodGet {
api.LogDebugf("Handling config request: %s", f.path)
f.mcpConfigHandler.HandleConfigRequest(f.req, []byte{})
return api.LocalReply
}
f.userLevelConfig = true
if endStream {
return api.Continue
} else {
return api.StopAndBuffer
}
}
if !strings.HasSuffix(url.ParsedURL.Path, GlobalSSEPathSuffix) {
f.proxyURL = url.ParsedURL
if f.config.enableUserLevelServer {
parts := strings.Split(url.ParsedURL.Path, "/")
if len(parts) >= 3 {
serverName := parts[1]
uid := parts[2]
// Get encoded config
encodedConfig, _ := f.mcpConfigHandler.GetEncodedConfig(serverName, uid)
if encodedConfig != "" {
header.Set("x-higress-mcpserver-config", encodedConfig)
api.LogDebugf("Set x-higress-mcpserver-config Header for %s:%s", serverName, uid)
}
}
f.ratelimit = true
}
if endStream {
return api.Continue
} else {
return api.StopAndBuffer
}
}
if url.Method != http.MethodGet {
f.callbacks.DecoderFilterCallbacks().SendLocalReply(http.StatusMethodNotAllowed, "Method not allowed", nil, 0, "")
} else {
f.config.defaultServer = common.NewSSEServer(common.NewMCPServer(DefaultServerName, Version),
common.WithSSEEndpoint(GlobalSSEPathSuffix),
common.WithMessageEndpoint(strings.TrimSuffix(url.ParsedURL.Path, GlobalSSEPathSuffix)),
common.WithRedisClient(common.GlobalRedisClient))
f.serverName = f.config.defaultServer.GetServerName()
body := "SSE connection create"
f.callbacks.DecoderFilterCallbacks().SendLocalReply(http.StatusOK, body, nil, 0, "")
}
return api.LocalReply
}
// DecodeData might be called multiple times during handling the request body.
// The endStream is true when handling the last piece of the body.
func (f *filter) DecodeData(buffer api.BufferInstance, endStream bool) api.StatusType {
if !f.neepProcess {
return api.Continue
}
if !endStream {
return api.StopAndBuffer
}
if f.userLevelConfig {
// Handle config POST request
api.LogDebugf("Handling config request: %s", f.path)
f.mcpConfigHandler.HandleConfigRequest(f.req, buffer.Bytes())
return api.LocalReply
} else if f.ratelimit {
if checkJSONRPCMethod(buffer.Bytes(), "tools/list") {
api.LogDebugf("Not a tools call request, skipping ratelimit")
return api.Continue
}
parts := strings.Split(f.req.URL.Path, "/")
if len(parts) < 3 {
api.LogWarnf("Access denied: no valid uid found")
f.callbacks.DecoderFilterCallbacks().SendLocalReply(http.StatusForbidden, "", nil, 0, "")
return api.LocalReply
}
serverName := parts[1]
uid := parts[2]
encodedConfig, err := f.mcpConfigHandler.GetEncodedConfig(serverName, uid)
if err != nil {
api.LogWarnf("Access denied: no valid config found for uid %s", uid)
f.callbacks.DecoderFilterCallbacks().SendLocalReply(http.StatusForbidden, "", nil, 0, "")
return api.LocalReply
} else if encodedConfig == "" && checkJSONRPCMethod(buffer.Bytes(), "tools/call") {
api.LogDebugf("Empty config found for %s:%s", serverName, uid)
if !f.mcpRatelimitHandler.HandleRatelimit(f.req, buffer.Bytes()) {
return api.LocalReply
}
}
}
return api.Continue
}
// Callbacks which are called in response path
// The endStream is true if the response doesn't have body
func (f *filter) EncodeHeaders(header api.ResponseHeaderMap, endStream bool) api.StatusType {
if !f.neepProcess {
return api.Continue
}
if f.serverName != "" {
if common.GlobalRedisClient != nil {
header.Set("Content-Type", "text/event-stream")
header.Set("Cache-Control", "no-cache")
header.Set("Connection", "keep-alive")
header.Set("Access-Control-Allow-Origin", "*")
header.Del("Content-Length")
} else {
header.Set("Content-Length", strconv.Itoa(len(RedisNotEnabledResponseBody)))
}
return api.Continue
}
return api.Continue
}
// EncodeData might be called multiple times during handling the response body.
// The endStream is true when handling the last piece of the body.
func (f *filter) EncodeData(buffer api.BufferInstance, endStream bool) api.StatusType {
if !f.neepProcess {
return api.Continue
}
if !endStream {
return api.StopAndBuffer
}
if f.proxyURL != nil && common.GlobalRedisClient != nil {
sessionID := f.proxyURL.Query().Get("sessionId")
if sessionID != "" {
channel := common.GetSSEChannelName(sessionID)
eventData := fmt.Sprintf("event: message\ndata: %s\n\n", buffer.String())
publishErr := common.GlobalRedisClient.Publish(channel, eventData)
if publishErr != nil {
api.LogErrorf("Failed to publish wasm mcp server message to Redis: %v", publishErr)
}
}
}
if f.serverName != "" {
if common.GlobalRedisClient != nil {
// handle default server
buffer.Reset()
f.config.defaultServer.HandleSSE(f.callbacks, f.stopChan)
return api.Running
} else {
buffer.SetString(RedisNotEnabledResponseBody)
return api.Continue
}
}
return api.Continue
}
// OnDestroy stops the goroutine
func (f *filter) OnDestroy(reason api.DestroyReason) {
api.LogDebugf("OnDestroy: reason=%v", reason)
if f.serverName != "" && f.stopChan != nil {
select {
case <-f.stopChan:
return
default:
api.LogDebug("Stopping SSE connection")
close(f.stopChan)
}
}
}
// check if the request is a tools/call request
func checkJSONRPCMethod(body []byte, method string) bool {
var request mcp.CallToolRequest
if err := json.Unmarshal(body, &request); err != nil {
api.LogWarnf("Failed to unmarshal request body: %v, not a JSON RPC request", err)
return true
}
return request.Method == method
}

View File

@@ -7,7 +7,7 @@ import (
"net/http"
"strings"
"github.com/alibaba/higress/plugins/golang-filter/mcp-server/internal"
"github.com/alibaba/higress/plugins/golang-filter/mcp-session/common"
"github.com/envoyproxy/envoy/contrib/golang/common/go/api"
)
@@ -18,7 +18,7 @@ type MCPConfigHandler struct {
}
// NewMCPConfigHandler creates a new instance of MCP configuration handler
func NewMCPConfigHandler(redisClient *internal.RedisClient, callbacks api.FilterCallbackHandler) *MCPConfigHandler {
func NewMCPConfigHandler(redisClient *common.RedisClient, callbacks api.FilterCallbackHandler) *MCPConfigHandler {
return &MCPConfigHandler{
configStore: NewRedisConfigStore(redisClient),
callbacks: callbacks,

View File

@@ -5,7 +5,7 @@ import (
"fmt"
"time"
"github.com/alibaba/higress/plugins/golang-filter/mcp-server/internal"
"github.com/alibaba/higress/plugins/golang-filter/mcp-session/common"
)
const (
@@ -36,11 +36,11 @@ type ConfigStore interface {
// RedisConfigStore implements configuration storage using Redis
type RedisConfigStore struct {
redisClient *internal.RedisClient
redisClient *common.RedisClient
}
// NewRedisConfigStore creates a new instance of Redis configuration storage
func NewRedisConfigStore(redisClient *internal.RedisClient) ConfigStore {
func NewRedisConfigStore(redisClient *common.RedisClient) ConfigStore {
return &RedisConfigStore{
redisClient: redisClient,
}
@@ -101,5 +101,11 @@ func (s *RedisConfigStore) GetConfig(serverName string, uid string) (map[string]
return nil, err
}
// Refresh TTL
if err := s.redisClient.Expire(key, configExpiry); err != nil {
// Log error but don't fail the request
fmt.Printf("Failed to refresh TTL for key %s: %v\n", key, err)
}
return config, nil
}

View File

@@ -8,13 +8,13 @@ import (
"strings"
"time"
"github.com/alibaba/higress/plugins/golang-filter/mcp-server/internal"
"github.com/alibaba/higress/plugins/golang-filter/mcp-session/common"
"github.com/envoyproxy/envoy/contrib/golang/common/go/api"
"github.com/mark3labs/mcp-go/mcp"
)
type MCPRatelimitHandler struct {
redisClient *internal.RedisClient
redisClient *common.RedisClient
callbacks api.FilterCallbackHandler
limit int // Maximum requests allowed per window
window int // Time window in seconds
@@ -31,7 +31,7 @@ type MCPRatelimitConfig struct {
}
// NewMCPRatelimitHandler creates a new rate limit handler
func NewMCPRatelimitHandler(redisClient *internal.RedisClient, callbacks api.FilterCallbackHandler, conf *MCPRatelimitConfig) *MCPRatelimitHandler {
func NewMCPRatelimitHandler(redisClient *common.RedisClient, callbacks api.FilterCallbackHandler, conf *MCPRatelimitConfig) *MCPRatelimitHandler {
if conf == nil {
conf = &MCPRatelimitConfig{
Limit: 100,

View File

@@ -6,11 +6,9 @@ replace quark-search => ../quark-search
replace amap-tools => ../amap-tools
replace github.com/alibaba/higress/plugins/wasm-go => ../..
require (
amap-tools v0.0.0-00010101000000-000000000000
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250417132640-fb2e8d5157ad
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250423015849-23258157a406
quark-search v0.0.0-00010101000000-000000000000
)

View File

@@ -6,6 +6,8 @@ github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+
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/alibaba/higress/plugins/wasm-go v1.4.4-0.20250423015849-23258157a406 h1:pWZsjfarQyUPlzJ9CMy4C5iHl0jb2jntscd1wCGwGB0=
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250423015849-23258157a406/go.mod h1:yObZXF1xTx/8peEsSbtHIzz7KlTr/tZCrokIHtwF0Jk=
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=

View File

@@ -28,7 +28,7 @@ On the user's MCP Client interface, add the generated SSE URL to the MCP Server
```json
"mcpServers": {
"bravesearch": {
"url": "http://mcp.higress.ai/mcp-bravesearch/{generate_key}",
"url": "https://mcp.higress.ai/mcp-bravesearch/{generate_key}",
}
}
```

View File

@@ -30,7 +30,7 @@
```json
"mcpServers": {
"bravesearch": {
"url": "http://mcp.higress.ai/mcp-bravesearch/{generate_key}",
"url": "https://mcp.higress.ai/mcp-bravesearch/{generate_key}",
}
}
```

View File

@@ -21,7 +21,7 @@ On the user's MCP Client interface, add the generated SSE URL to the MCP Server
```json
"mcpServers": {
"chatppt": {
"url": "http://mcp.higress.ai/mcp-chatppt/{generate_key}",
"url": "https://mcp.higress.ai/mcp-chatppt/{generate_key}",
}
}
```

View File

@@ -21,7 +21,7 @@
```json
"mcpServers": {
"chatppt": {
"url": "http://mcp.higress.ai/mcp-chatppt/{generate_key}",
"url": "https://mcp.higress.ai/mcp-chatppt/{generate_key}",
}
}
```

View File

@@ -0,0 +1,40 @@
# E2BDev MCP Server
An implementation of the Model Context Protocol (MCP) server that integrates E2B Code Interpreter API, providing sandbox environment management capabilities, which enables execution of Python code.
## Usage Guide
### Get API-KEY
1. Register for an E2B account [Resigter Entry](https://e2b.dev/auth/sign-up). Each new account will receive 100 credits for free.
2. Generate API Key in Dashboard [Manage API-KEY](https://e2b.dev/dashboard?tab=keys)
### Configure MCP Client
On the user's MCP Client interface, add E2BDev MCP Server configuration.
```json
"mcpServers": {
"e2bdev": {
"url": "https://mcp.higress.ai/mcp-e2bdev/{generate_key}",
}
}
```
### Tools
- **create_sandbox**: Create E2B sandbox environment
- Parameters:
- timeout: Sandbox timeout in seconds, sandbox will be terminated after timeout
- Returns: Sandbox ID
- **execute_code_sandbox**: Execute code in sandbox
- Parameters:
- sandbox_id: Sandbox ID, obtained from create_sandbox
- code: Python code to execute
- Returns: Execution result
- **kill_sandbox**: Terminate sandbox environment
- Parameters:
- sandbox_id: Sandbox ID to terminate
- Returns: Termination result

View File

@@ -0,0 +1,39 @@
# E2BDev MCP Server
基于E2B Code Interpreter API 的MCP服务器实现提供沙盒环境管理功能沙盒环境中可执行Python代码。
## 使用教程
### 获取 API-KEY
1. 注册E2B账号 [注册入口](https://e2b.dev/auth/sign-up),每位新用户有$100的免费额度。
2. 在DashBoard中生成 API Key [生成 API Key](https://e2b.dev/dashboard?tab=keys)
### 配置 MCP Client
在用户的 MCP Client 界面,添加 E2BDev MCP Server 配置。
```json
"mcpServers": {
"e2bdev": {
"url": "https://mcp.higress.ai/mcp-e2bdev/{generate_key}",
}
}
```
### 工具使用
- **create_sandbox**: 创建E2B沙盒环境
- 参数:
- timeout: 沙盒超时时间(秒),超时后沙盒将被终止
- 返回: 沙盒ID
- **execute_code_sandbox**: 在沙盒中执行代码
- 参数:
- sandbox_id: 沙盒ID从create_sandbox获取
- code: 要执行的Python代码
- 返回: 执行结果
- **kill_sandbox**: 终止沙盒环境
- 参数:
- sandbox_id: 要终止的沙盒ID
- 返回: 终止结果

View File

@@ -0,0 +1,87 @@
server:
name: e2bdev-api-server
config:
apiKey: ""
tools:
- name: create_sandbox
description: Create e2b sandbox and return sandboxID
args:
- name: templateID
description: "type of sandbox, fixed parameter"
type: string
enum: ["code-interpreter-beta"]
- name: timeout
description: "sanbox timeout in seconds, sanbox will be killed after timeout."
type: int
required: true
default: 300
requestTemplate:
url: https://api.e2b.dev/sandboxes
method: POST
argsToJsonBody: true
headers:
- key: Content-Type
value: "application/json"
- key: X-API-key
value: "{{.config.apiKey}}"
responseTemplate:
body: |
{
"sandboxID": "{{.sandboxID}}-{{.clientID}}"
}
- name: execute_code_sandbox
description: Execute code in e2b sandbox
args:
- name: sandbox_id
description: "create sandbox id, get from create_sandbox"
type: string
required: true
position: path
- name: code
description: "python code to execute"
type: string
required: true
position: body
requestTemplate:
url: "https://49999-{{.args.sandbox_id}}.e2b.dev/execute"
method: POST
argsToJsonBody: true
headers:
- key: Content-Type
value: "application/json"
- key: Authorization
value: "Bearer {{.config.apiKey}}"
responseTemplate:
prependBody: |+
# API Response Information
Below is the response from an API call. To help you understand the data, I've provided:
1. A detailed description of all fields in the response structure
2. The complete API response
## Response Structure
> Content-Type: application/json
result.type is valid only when in ["stdout", "stdout", "result", "error"]
- **result: **: (Type: object)
- **result.type**: (Type: string)
- **result.text**: (Type: string)
## Original Response
- name: kill_sandbox
description: Kill e2b sandbox
args:
- name: sandbox_id
description: "sandbox id, get from get_sandbox_id"
type: string
required: true
position: path
requestTemplate:
url: https://api.e2b.dev/sandboxes/{{.args.sandbox_id}}
method: DELETE
headers:
- key: X-API-key
value: "{{.config.apiKey}}"

View File

@@ -27,7 +27,7 @@ On the user's MCP Client interface, add the generated SSE URL to the MCP Server
```json
"mcpServers": {
"firecrawl": {
"url": "http://mcp.higress.ai/mcp-firecrawl/{generate_key}",
"url": "https://mcp.higress.ai/mcp-firecrawl/{generate_key}",
}
}
```

View File

@@ -27,7 +27,7 @@
```json
"mcpServers": {
"firecrawl": {
"url": "http://mcp.higress.ai/mcp-firecrawl/{generate_key}",
"url": "https://mcp.higress.ai/mcp-firecrawl/{generate_key}",
}
}
```

View File

@@ -33,7 +33,7 @@ On the user's MCP Client interface, add the generated SSE URL to the MCP Server
```json
"mcpServers": {
"github": {
"url": "http://mcp.higress.ai/mcp-github/{generate_key}",
"url": "https://mcp.higress.ai/mcp-github/{generate_key}",
}
}
```

View File

@@ -33,7 +33,7 @@ GitHub API 的 MCP 服务器实现,支持文件操作、仓库管理、搜索
```json
"mcpServers": {
"github": {
"url": "http://mcp.higress.ai/mcp-github/{generate_key}",
"url": "https://mcp.higress.ai/mcp-github/{generate_key}",
}
}
```

View File

@@ -52,6 +52,8 @@ tools:
value: "application/vnd.github+json"
- key: X-GitHub-Api-Version
value: "2022-11-28"
- key: User-Agent
value: "higress-mcp"
- name: search_repositories
description: 搜索GitHub仓库
@@ -78,6 +80,8 @@ tools:
value: "application/vnd.github+json"
- key: X-GitHub-Api-Version
value: "2022-11-28"
- key: User-Agent
value: "higress-mcp"
- name: create_repository
description: 在您的账户中创建新的GitHub仓库
@@ -115,6 +119,8 @@ tools:
value: "application/vnd.github+json"
- key: X-GitHub-Api-Version
value: "2022-11-28"
- key: User-Agent
value: "higress-mcp"
- name: get_file_contents
description: 从GitHub仓库获取文件或目录内容
@@ -145,6 +151,8 @@ tools:
value: "application/vnd.github+json"
- key: X-GitHub-Api-Version
value: "2022-11-28"
- key: User-Agent
value: "higress-mcp"
- name: push_files
description: 在单个提交中推送多个文件到GitHub仓库
@@ -192,6 +200,8 @@ tools:
value: "application/vnd.github+json"
- key: X-GitHub-Api-Version
value: "2022-11-28"
- key: User-Agent
value: "higress-mcp"
- name: create_issue
description: 在GitHub仓库创建新Issue
@@ -246,6 +256,8 @@ tools:
value: "application/vnd.github+json"
- key: X-GitHub-Api-Version
value: "2022-11-28"
- key: User-Agent
value: "higress-mcp"
- name: create_pull_request
description: 在GitHub仓库创建新的Pull Request
@@ -301,6 +313,8 @@ tools:
value: "application/vnd.github+json"
- key: X-GitHub-Api-Version
value: "2022-11-28"
- key: User-Agent
value: "higress-mcp"
- name: fork_repository
description: 将GitHub仓库fork到您的账户或指定组织
@@ -331,6 +345,8 @@ tools:
value: "application/vnd.github+json"
- key: X-GitHub-Api-Version
value: "2022-11-28"
- key: User-Agent
value: "higress-mcp"
- name: create_branch
description: 在GitHub仓库创建新分支
@@ -366,6 +382,8 @@ tools:
value: "application/vnd.github+json"
- key: X-GitHub-Api-Version
value: "2022-11-28"
- key: User-Agent
value: "higress-mcp"
- name: list_commits
description: 获取GitHub仓库分支的提交列表
@@ -400,6 +418,8 @@ tools:
value: "application/vnd.github+json"
- key: X-GitHub-Api-Version
value: "2022-11-28"
- key: User-Agent
value: "higress-mcp"
- name: list_issues
description: 列出并过滤GitHub仓库的Issues
@@ -452,6 +472,8 @@ tools:
value: "application/vnd.github+json"
- key: X-GitHub-Api-Version
value: "2022-11-28"
- key: User-Agent
value: "higress-mcp"
- name: update_issue
description: 更新GitHub仓库中的现有Issue
@@ -515,6 +537,8 @@ tools:
value: "application/vnd.github+json"
- key: X-GitHub-Api-Version
value: "2022-11-28"
- key: User-Agent
value: "higress-mcp"
- name: add_issue_comment
description: 在GitHub Issue中添加评论
@@ -549,6 +573,8 @@ tools:
value: "application/vnd.github+json"
- key: X-GitHub-Api-Version
value: "2022-11-28"
- key: User-Agent
value: "higress-mcp"
- name: search_code
description: 在GitHub仓库中搜索代码
@@ -583,6 +609,8 @@ tools:
value: "application/vnd.github+json"
- key: X-GitHub-Api-Version
value: "2022-11-28"
- key: User-Agent
value: "higress-mcp"
- name: search_issues
description: 在GitHub仓库中搜索Issues和Pull Requests
@@ -617,6 +645,8 @@ tools:
value: "application/vnd.github+json"
- key: X-GitHub-Api-Version
value: "2022-11-28"
- key: User-Agent
value: "higress-mcp"
- name: search_users
description: 在GitHub中搜索用户
@@ -651,6 +681,8 @@ tools:
value: "application/vnd.github+json"
- key: X-GitHub-Api-Version
value: "2022-11-28"
- key: User-Agent
value: "higress-mcp"
- name: get_issue
description: 获取GitHub仓库中特定Issue的详细信息
@@ -677,6 +709,8 @@ tools:
value: "application/vnd.github+json"
- key: X-GitHub-Api-Version
value: "2022-11-28"
- key: User-Agent
value: "higress-mcp"
- name: get_pull_request
description: 获取GitHub仓库中特定Pull Request的详细信息
@@ -703,6 +737,8 @@ tools:
value: "application/vnd.github+json"
- key: X-GitHub-Api-Version
value: "2022-11-28"
- key: User-Agent
value: "higress-mcp"
- name: list_pull_requests
description: 列出并过滤GitHub仓库的Pull Requests
@@ -753,6 +789,8 @@ tools:
value: "application/vnd.github+json"
- key: X-GitHub-Api-Version
value: "2022-11-28"
- key: User-Agent
value: "higress-mcp"
- name: create_pull_request_review
description: 在GitHub Pull Request上创建review
@@ -814,6 +852,8 @@ tools:
value: "application/vnd.github+json"
- key: X-GitHub-Api-Version
value: "2022-11-28"
- key: User-Agent
value: "higress-mcp"
- name: merge_pull_request
description: 合并GitHub Pull Request
@@ -858,6 +898,8 @@ tools:
value: "application/vnd.github+json"
- key: X-GitHub-Api-Version
value: "2022-11-28"
- key: User-Agent
value: "higress-mcp"
- name: get_pull_request_files
description: 获取GitHub Pull Request中更改的文件列表
@@ -884,6 +926,8 @@ tools:
value: "application/vnd.github+json"
- key: X-GitHub-Api-Version
value: "2022-11-28"
- key: User-Agent
value: "higress-mcp"
- name: get_pull_request_status
description: 获取GitHub Pull Request的状态检查结果
@@ -910,6 +954,8 @@ tools:
value: "application/vnd.github+json"
- key: X-GitHub-Api-Version
value: "2022-11-28"
- key: User-Agent
value: "higress-mcp"
- name: update_pull_request_branch
description: 使用base分支的最新更改更新Pull Request分支
@@ -944,6 +990,8 @@ tools:
value: "application/vnd.github+json"
- key: X-GitHub-Api-Version
value: "2022-11-28"
- key: User-Agent
value: "higress-mcp"
- name: get_pull_request_comments
description: 获取GitHub Pull Request的review评论
@@ -970,6 +1018,8 @@ tools:
value: "application/vnd.github+json"
- key: X-GitHub-Api-Version
value: "2022-11-28"
- key: User-Agent
value: "higress-mcp"
- name: get_pull_request_reviews
description: 获取GitHub Pull Request的reviews
@@ -996,3 +1046,5 @@ tools:
value: "application/vnd.github+json"
- key: X-GitHub-Api-Version
value: "2022-11-28"
- key: User-Agent
value: "higress-mcp"

View File

@@ -25,7 +25,7 @@ On the user's MCP Client interface, add the generated SSE URL to the MCP Server
```json
"mcpServers": {
"librechat": {
"url": "http://mcp.higress.ai/mcp-librechat/{generate_key}",
"url": "https://mcp.higress.ai/mcp-librechat/{generate_key}",
}
}
```

View File

@@ -25,7 +25,7 @@
```json
"mcpServers": {
"librechat": {
"url": "http://mcp.higress.ai/mcp-librechat/{generate_key}",
"url": "https://mcp.higress.ai/mcp-librechat/{generate_key}",
}
}
```

View File

@@ -32,7 +32,7 @@ On the user's MCP Client interface, add the generated SSE URL to the MCP Server
```json
"mcpServers": {
"notion": {
"url": "http://mcp.higress.ai/mcp-notion/{generate_key}",
"url": "https://mcp.higress.ai/mcp-notion/{generate_key}",
}
}
```

View File

@@ -32,7 +32,7 @@ Notion MCP Server 提供了以下功能:
```json
"mcpServers": {
"notion": {
"url": "http://mcp.higress.ai/mcp-notion/{generate_key}",
"url": "https://mcp.higress.ai/mcp-notion/{generate_key}",
}
}
```

View File

@@ -17,7 +17,7 @@ On the user's MCP Client interface, add the generated SSE URL to the MCP Server
```json
"mcpServers": {
"weather": {
"url": "http://mcp.higress.ai/mcp-weather/{generate_key}",
"url": "https://mcp.higress.ai/mcp-weather/{generate_key}",
}
}
```

View File

@@ -17,7 +17,7 @@
```json
"mcpServers": {
"weather": {
"url": "http://mcp.higress.ai/mcp-weather/{generate_key}",
"url": "https://mcp.higress.ai/mcp-weather/{generate_key}",
}
}
```

View File

@@ -27,6 +27,6 @@ On the user's MCP Client interface, add the generated SSE URL to the MCP Server
```json
"mcpServers": {
"wolframalpha": {
"url": "http://mcp.higress.ai/mcp-wolframalpha/{generate_key}",
"url": "https://mcp.higress.ai/mcp-wolframalpha/{generate_key}",
}
}

View File

@@ -27,6 +27,6 @@
```json
"mcpServers": {
"wolframalpha": {
"url": "http://mcp.higress.ai/mcp-wolframalpha/{generate_key}",
"url": "https://mcp.higress.ai/mcp-wolframalpha/{generate_key}",
}
}

View File

@@ -67,25 +67,5 @@ tools:
headers:
- key: Authorization
value: "Bearer {{.config.appid}}"
responseTemplate:
prependBody: |+
# API Response Information
Below is the response from an API call. To help you understand the data, I've provided:
1. A detailed description of all fields in the response structure
2. The complete API response
## Response Structure
> Content-Type: application/json
- **images**: List of image URLs related to the query. (Type: array)
- **images[]**: Items of type string
- **inputInterpretation**: WolframAlpha's interpretation of the input query. (Type: string)
- **link**: A link back to the full WolframAlpha results page for this query. (Type: string)
- **query**: The query that was submitted. (Type: string)
- **result**: The computed result for the query. (Type: string)
## Original Response

View File

@@ -0,0 +1,37 @@
# Yuque MCP Server
Implementation of the MCP server based on the Yuque Open Service API, enabling the editing, updating, and publishing of Yuque knowledge bases and documents through the MCP protocol.
## Features
Currently supports the following operations:
- **Knowledge Base Management**: Create, search, update, delete knowledge bases, etc.
- **Document Management**: Create, update, view history details, search documents, etc.
For enterprise team users:
- **Member Management**: Manage knowledge base members and permissions.
- **Data Aggregation**: Statistics on knowledge bases, documents, members, etc.
## Usage Guide
### Get AccessToken
Refer to the [Yuque Developer Documentation](https://www.yuque.com/yuque/developer/api) for personal user authentication or enterprise team identity authentication.
### Generate SSE URL
On the MCP Server interface, log in and enter the AccessToken to generate the URL.
### Configure MCP Client
On the user's MCP Client interface, add the generated SSE URL to the MCP Server list.
```json
"mcpServers": {
"yuque": {
"url": "https://mcp.higress.ai/mcp-yuque/{generate_key}",
}
}
```

View File

@@ -0,0 +1,38 @@
# 语雀 MCP Server
基于语雀开放服务 API 的 MCP 服务器实现,通过 MCP 协议,实现语雀知识库、文档的编辑、更新发布。
## 功能
当前支持以下操作:
- **知识库管理**:新建、搜索、更新、删除知识库等。
- **文档管理**:创建、更新、历史详情、搜索文档等。
对于企业团队用户:
- **成员管理**:管理知识库成员、权限。
- **数据汇总**:知识库、文档、成员等数据统计。
## 使用教程
### 获取 AccessToken
参考[语雀开发者文档](https://www.yuque.com/yuque/developer/api),进行个人用户认证或企业团队身份认证。
### 生成 SSE URL
在 MCP Server 界面,登录后输入 AccessToken生成URL。
### 配置 MCP Client
在用户的 MCP Client 界面,将生成的 SSE URL添加到 MCP Server列表中。
```json
"mcpServers": {
"yuque": {
"url": "https://mcp.higress.ai/mcp-yuque/{generate_key}",
}
}
```

View File

File diff suppressed because one or more lines are too long

View File

@@ -202,6 +202,7 @@ pub struct AiDataMaskingConfig {
#[derive(Debug, Deserialize, Clone)]
struct Message {
#[serde(default)]
content: String,
}
#[derive(Debug, Deserialize, Clone)]
@@ -221,8 +222,11 @@ struct ResMessage {
#[derive(Default, Debug, Deserialize, Serialize, Clone)]
struct Usage {
#[serde(default)]
completion_tokens: i32,
#[serde(default)]
prompt_tokens: i32,
#[serde(default)]
total_tokens: i32,
}

View File

@@ -299,7 +299,9 @@ data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872012
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872012,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"。"},"finish_reason":null}]}
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872012,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop"}]}
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872012,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{}}
data: {"id":"chatcmpl-936","object":"chat.completion.chunk","created":1739872012,"model":"qwen2.5-coder:32b","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{}}],"usage":{"prompt_tokens":372,"completion_tokens":9,"total_tokens":381}}
data: [DONE]

155
registry/mcp_model.go Normal file
View File

@@ -0,0 +1,155 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package registry
const (
JsonGoTemplateType = "json-go-template"
IstioMcpAutoGeneratedPrefix = "istio-autogenerated-mcp"
IstioMcpAutoGeneratedVsName = IstioMcpAutoGeneratedPrefix + "-vs"
IstioMcpAutoGeneratedSeName = IstioMcpAutoGeneratedPrefix + "-se"
IstioMcpAutoGeneratedDrName = IstioMcpAutoGeneratedPrefix + "-dr"
IstioMcpAutoGeneratedHttpRouteName = IstioMcpAutoGeneratedPrefix + "-httproute"
DefaultMcpToolsGroup = "mcp-tools"
DefaultMcpCredentialsGroup = "credentials"
DefaultNacosServiceNamespace = "public"
StdioProtocol = "stdio"
HttpProtocol = "http"
DubboProtocol = "dubbo"
McpSSEProtocol = "mcp-sse"
McpStreambleProtocol = "mcp-streamble"
)
type McpToolArgsType string
// WasmPluginConfig Struct for mcp tool wasm plugin marshal
type WasmPluginConfig struct {
Rules []*McpServerRule `json:"_rules_"`
}
type McpServerRule struct {
MatchRoute []string `json:"_match_route_,omitempty"`
Server *ServerConfig `json:"server"`
Tools []*McpTool `json:"tools"`
}
type ServerConfig struct {
Name string `json:"name,omitempty"`
Config map[string]interface{} `json:"config,omitempty"`
AllowTools []string `json:"allowTools,omitempty"`
}
type McpTool struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Args []*ToolArgs `json:"args,omitempty"`
RequestTemplate *RequestTemplate `json:"requestTemplate"`
ResponseTemplate *ResponseTemplate `json:"responseTemplate"`
}
type ToolArgs struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Type string `json:"type,omitempty"`
Required bool `json:"required,omitempty"`
Default interface{} `json:"default,omitempty"`
Enum []interface{} `json:"enum,omitempty"`
Items []interface{} `json:"items,omitempty"`
Properties interface{} `json:"properties,omitempty"`
Position string `json:"position,omitempty"`
}
type RequestTemplate struct {
URL string `json:"url"`
Method string `json:"method"`
Headers []*RequestTemplateHeaders `json:"headers,omitempty"`
Body string `json:"body,omitempty"`
ArgsToJsonBody bool `json:"argsToJsonBody,omitempty"`
ArgsToUrlParam bool `json:"argsToUrlParam,omitempty"`
ArgsToFormBody bool `json:"argsToFormBody,omitempty"`
}
type RequestTemplateHeaders struct {
Key string `json:"key"`
Value string `json:"value"`
}
type ResponseTemplate struct {
Body string `json:"body,omitempty"`
PrependBody string `json:"prependBody,omitempty"`
AppendBody string `json:"appendBody,omitempty"`
}
// McpServer Struct for mcp server json unmarshal
type McpServer struct {
Protocol string `json:"protocol,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Version string `json:"version,omitempty"`
Enabled bool `json:"enabled,omitempty"`
RemoteServerConfig *RemoteServerConfig `json:"remoteServerConfig,omitempty"`
Credentials map[string]*CredentialRef `json:"credentials,omitempty"`
ToolsDescriptionRef string `json:"toolsDescriptionRef,omitempty"`
PromptDescriptionRef string `json:"promptDescriptionRef,omitempty"`
ResourceDescriptionRef string `json:"resourceDescriptionRef,omitempty"`
}
type RemoteServerConfig struct {
ServiceRef *ServiceRef `json:"serviceRef,omitempty"`
ExportPath string `json:"exportPath,omitempty"`
BackendProtocol string `json:"backendProtocol,omitempty"`
}
type CredentialRef struct {
Ref string `json:"ref,omitempty"`
}
type ServiceRef struct {
NamespaceId string `json:"namespaceId,omitempty"`
GroupName string `json:"groupName,omitempty"`
ServiceName string `json:"serviceName,omitempty"`
}
// McpToolConfig Struct for mcp tool json unmarshal
type McpToolConfig struct {
Tools []*ToolDescription `json:"tools,omitempty"`
ToolsMeta map[string]*ToolsMeta `json:"toolsMeta,omitempty"`
}
type ToolDescription struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
InputSchema InputSchema `json:"inputSchema"`
}
type InputSchema struct {
Type string `json:"type,omitempty"`
Properties map[string]interface{} `json:"properties,omitempty"`
Required []string `json:"required,omitempty"`
}
type ToolsMeta struct {
InvokeContext map[string]string `json:"InvokeContext,omitempty"`
Enabled bool `json:"Enabled,omitempty"`
Templates map[string]interface{} `json:"Templates,omitempty"`
}
type JsonGoTemplate struct {
RequestTemplate RequestTemplate `json:"requestTemplate"`
ResponseTemplate ResponseTemplate `json:"responseTemplate"`
ArgsPosition map[string]string `json:"argsPosition,omitempty"`
}

View File

@@ -15,12 +15,21 @@
package memory
import (
"encoding/json"
"sort"
"strconv"
"sync"
"time"
higressconfig "github.com/alibaba/higress/pkg/config"
"github.com/alibaba/higress/registry"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/wrapperspb"
extensions "istio.io/api/extensions/v1alpha1"
"istio.io/api/networking/v1alpha3"
"istio.io/istio/pkg/config"
"istio.io/istio/pkg/config/schema/gvk"
"istio.io/pkg/log"
"github.com/alibaba/higress/pkg/common"
@@ -30,6 +39,8 @@ import (
type Cache interface {
UpdateServiceWrapper(service string, data *ServiceWrapper)
DeleteServiceWrapper(service string)
UpdateConfigCache(kind config.GroupVersionKind, key string, config *config.Config, forceDelete bool)
GetAllConfigs(kind config.GroupVersionKind) map[string]*config.Config
PurgeStaleService()
UpdateServiceEntryEndpointWrapper(service, ip, regionId, zoneId, protocol string, labels map[string]string)
GetServiceByEndpoints(requestVersions, endpoints map[string]bool, versionKey string, protocol common.Protocol) map[string][]string
@@ -44,6 +55,7 @@ func NewCache() Cache {
return &store{
mux: &sync.RWMutex{},
sew: make(map[string]*ServiceWrapper),
configs: make(map[string]map[string]*config.Config),
toBeUpdated: make([]*ServiceWrapper, 0),
toBeDeleted: make([]*ServiceWrapper, 0),
ip2services: make(map[string]map[string]bool),
@@ -54,12 +66,85 @@ func NewCache() Cache {
type store struct {
mux *sync.RWMutex
sew map[string]*ServiceWrapper
configs map[string]map[string]*config.Config
toBeUpdated []*ServiceWrapper
toBeDeleted []*ServiceWrapper
ip2services map[string]map[string]bool
deferedDelete map[string]struct{}
}
func (s *store) GetAllConfigs(kind config.GroupVersionKind) map[string]*config.Config {
s.mux.Lock()
defer s.mux.Unlock()
cfgs, exist := s.configs[kind.String()]
if !exist {
return map[string]*config.Config{}
}
if kind == gvk.WasmPlugin {
pluginConfig := &registry.WasmPluginConfig{}
var ns string
for _, cfg := range cfgs {
ns = cfg.Namespace
rule := cfg.Spec.(*registry.McpServerRule)
pluginConfig.Rules = append(pluginConfig.Rules, rule)
}
rulesBytes, err := json.Marshal(pluginConfig)
if err != nil {
log.Errorf("marshal mcp wasm plugin config error %v", err)
return map[string]*config.Config{}
}
pbs := &structpb.Struct{}
if err = protojson.Unmarshal(rulesBytes, pbs); err != nil {
log.Errorf("unmarshal mcp wasm plugin config error %v", err)
return map[string]*config.Config{}
}
wasmPlugin := &extensions.WasmPlugin{
ImagePullPolicy: extensions.PullPolicy_Always,
Phase: extensions.PluginPhase_UNSPECIFIED_PHASE,
Priority: &wrapperspb.Int32Value{Value: 30},
PluginConfig: pbs,
Url: higressconfig.McpServerWasmImageUrl,
}
return map[string]*config.Config{"wasm": &config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.WasmPlugin,
Name: "istio-autogenerated-mcp-wasmplugin",
Namespace: ns,
},
Spec: wasmPlugin,
}}
}
return cfgs
}
func (s *store) UpdateConfigCache(kind config.GroupVersionKind, key string, cfg *config.Config, forceDelete bool) {
if cfg == nil && !forceDelete {
return
}
s.mux.Lock()
if forceDelete {
for _, allConfigs := range s.configs {
delete(allConfigs, key)
}
log.Infof("Delete config %s in cache", key)
} else {
if _, exist := s.configs[kind.String()]; !exist {
s.configs[kind.String()] = make(map[string]*config.Config)
}
if _, exist := s.configs[kind.String()][key]; exist {
log.Infof("Update kind %s config %s", kind.String(), key)
} else {
log.Infof("Add kind %s config %s", kind.String(), key)
}
s.configs[kind.String()][key] = cfg
}
s.mux.Unlock()
}
func (s *store) UpdateServiceEntryEndpointWrapper(service, ip, regionId, zoneId, protocol string, labels map[string]string) {
s.mux.Lock()
defer s.mux.Unlock()
@@ -226,6 +311,15 @@ func (s *store) GetAllDestinationRuleWrapper() []*ingress.WrapperDestinationRule
drwList = append(drwList, serviceEntryWrapper.DeepCopy().DestinationRuleWrapper)
}
}
configFromMcp := s.configs[gvk.DestinationRule.String()]
for _, cfg := range configFromMcp {
dr := cfg.Spec.(*v1alpha3.DestinationRule)
drwList = append(drwList, &ingress.WrapperDestinationRule{
DestinationRule: dr,
ServiceKey: ingress.ServiceKey{ServiceFQDN: dr.Host},
})
}
return drwList
}

View File

@@ -0,0 +1,180 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mcpserver
import (
"fmt"
"github.com/nacos-group/nacos-sdk-go/v2/clients/config_client"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
)
type MultiConfigListener struct {
configClient config_client.IConfigClient
onChange func(map[string]string)
configCache map[string]string
innerCallback func(string, string, string, string)
}
func NewMultiConfigListener(configClient config_client.IConfigClient, onChange func(map[string]string)) *MultiConfigListener {
result := &MultiConfigListener{
configClient: configClient,
configCache: make(map[string]string),
onChange: onChange,
}
result.innerCallback = func(namespace string, group string, dataId string, content string) {
result.configCache[group+DefaultJoiner+dataId] = content
result.onChange(result.configCache)
}
return result
}
func (l *MultiConfigListener) StartListen(configs []vo.ConfigParam) error {
for _, config := range configs {
content, err := l.configClient.GetConfig(vo.ConfigParam{
DataId: config.DataId,
Group: config.Group,
})
if err != nil {
return fmt.Errorf("get config %s/%s err: %v", config.Group, config.DataId, err)
}
l.configCache[config.Group+DefaultJoiner+config.DataId] = content
err = l.configClient.ListenConfig(vo.ConfigParam{
DataId: config.DataId,
Group: config.Group,
OnChange: l.innerCallback,
})
if err != nil {
return fmt.Errorf("listener to config %s/%s error: %w", config.Group, config.DataId, err)
}
}
l.onChange(l.configCache)
return nil
}
func (l *MultiConfigListener) Stop() {
l.configClient.CloseClient()
}
func (l *MultiConfigListener) CancelListen(configs []vo.ConfigParam) error {
for _, config := range configs {
if _, ok := l.configCache[config.Group+DefaultJoiner+config.DataId]; ok {
err := l.configClient.CancelListenConfig(vo.ConfigParam{
DataId: config.DataId,
Group: config.Group,
})
if err != nil {
return fmt.Errorf("cancel config %s/%s error: %w", config.Group, config.DataId, err)
}
delete(l.configCache, config.Group+config.DataId)
}
}
return nil
}
type ServiceCache struct {
services map[string]*NacosServiceRef
client naming_client.INamingClient
}
type NacosServiceRef struct {
refs map[string]func([]model.Instance)
callback func(services []model.Instance, err error)
instances *[]model.Instance
}
func NewServiceCache(client naming_client.INamingClient) *ServiceCache {
return &ServiceCache{
client: client,
services: make(map[string]*NacosServiceRef),
}
}
func (c *ServiceCache) AddListener(group string, serviceName string, key string, callback func([]model.Instance)) error {
uniqueServiceName := c.makeServiceUniqueName(group, serviceName)
if _, ok := c.services[uniqueServiceName]; !ok {
instances, err := c.client.SelectAllInstances(vo.SelectAllInstancesParam{
GroupName: group,
ServiceName: serviceName,
})
if err != nil {
return err
}
ref := &NacosServiceRef{
refs: map[string]func([]model.Instance){},
instances: &instances,
}
ref.callback = func(services []model.Instance, err error) {
ref.instances = &services
for _, refCallback := range ref.refs {
refCallback(*ref.instances)
}
}
c.services[uniqueServiceName] = ref
err = c.client.Subscribe(&vo.SubscribeParam{
GroupName: group,
ServiceName: serviceName,
SubscribeCallback: ref.callback,
})
if err != nil {
return err
}
}
ref := c.services[uniqueServiceName]
ref.refs[key] = callback
callback(*ref.instances)
return nil
}
func (c *ServiceCache) RemoveListener(group string, serviceName string, key string) error {
if ref, ok := c.services[c.makeServiceUniqueName(group, serviceName)]; ok {
delete(ref.refs, key)
if len(ref.refs) == 0 {
err := c.client.Unsubscribe(&vo.SubscribeParam{
GroupName: group,
ServiceName: serviceName,
SubscribeCallback: ref.callback,
})
delete(c.services, c.makeServiceUniqueName(group, serviceName))
if err != nil {
return err
}
}
}
return nil
}
func (c *ServiceCache) makeServiceUniqueName(group string, serviceName string) string {
return fmt.Sprintf("%s-%s", group, serviceName)
}
func (c *ServiceCache) Stop() {
c.client.CloseClient()
}

View File

@@ -0,0 +1,989 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mcpserver
import (
"encoding/json"
"errors"
"fmt"
"net"
"strconv"
"strings"
"sync"
"time"
apiv1 "github.com/alibaba/higress/api/networking/v1"
"github.com/alibaba/higress/pkg/common"
common2 "github.com/alibaba/higress/pkg/ingress/kube/common"
provider "github.com/alibaba/higress/registry"
"github.com/alibaba/higress/registry/memory"
"github.com/golang/protobuf/ptypes/wrappers"
"github.com/nacos-group/nacos-sdk-go/v2/clients"
"github.com/nacos-group/nacos-sdk-go/v2/clients/config_client"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
"go.uber.org/atomic"
"istio.io/api/networking/v1alpha3"
"istio.io/istio/pkg/config"
"istio.io/istio/pkg/config/constants"
"istio.io/istio/pkg/config/schema/gvk"
"istio.io/istio/pkg/log"
"istio.io/istio/pkg/util/sets"
)
const (
DefaultInitTimeout = time.Second * 10
DefaultNacosTimeout = 5000
DefaultNacosLogLevel = "info"
DefaultNacosLogDir = "/var/log/nacos/log/mcp/log"
DefaultNacosCacheDir = "/var/log/nacos/log/mcp/cache"
DefaultNacosNotLoadCache = true
DefaultNacosLogMaxAge = 3
DefaultRefreshInterval = time.Second * 30
DefaultRefreshIntervalLimit = time.Second * 10
DefaultFetchPageSize = 50
DefaultJoiner = "@@"
NacosV3LabelKey = "isV3"
)
var mcpServerLog = log.RegisterScope("McpServer", "Nacos Mcp Server Watcher process.")
type watcher struct {
provider.BaseWatcher
apiv1.RegistryConfig
watchingConfig map[string]bool
watchingConfigRefs map[string]sets.Set[string]
configToConfigListener map[string]*MultiConfigListener
serviceCache map[string]*ServiceCache
configToService map[string]string
credentialKeyToName map[string]map[string]string
RegistryType provider.ServiceRegistryType
Status provider.WatcherStatus
configClient config_client.IConfigClient
serverConfig []constant.ServerConfig
cache memory.Cache
mutex *sync.Mutex
subMutex *sync.Mutex
callbackMutex *sync.Mutex
stop chan struct{}
isStop bool
updateCacheWhenEmpty bool
nacosClientConfig *constant.ClientConfig
namespace string
clusterId string
}
type WatcherOption func(w *watcher)
func NewWatcher(cache memory.Cache, opts ...WatcherOption) (provider.Watcher, error) {
w := &watcher{
watchingConfig: make(map[string]bool),
configToService: make(map[string]string),
watchingConfigRefs: make(map[string]sets.Set[string]),
configToConfigListener: make(map[string]*MultiConfigListener),
credentialKeyToName: make(map[string]map[string]string),
serviceCache: map[string]*ServiceCache{},
RegistryType: "nacos3",
Status: provider.UnHealthy,
cache: cache,
mutex: &sync.Mutex{},
subMutex: &sync.Mutex{},
callbackMutex: &sync.Mutex{},
stop: make(chan struct{}),
}
w.NacosRefreshInterval = int64(DefaultRefreshInterval)
for _, opt := range opts {
opt(w)
}
if w.NacosNamespace == "" {
w.NacosNamespace = w.NacosNamespaceId
}
mcpServerLog.Infof("new nacos mcp server watcher with config Name:%s", w.Name)
w.nacosClientConfig = constant.NewClientConfig(
constant.WithTimeoutMs(DefaultNacosTimeout),
constant.WithLogLevel(DefaultNacosLogLevel),
constant.WithLogDir(DefaultNacosLogDir),
constant.WithCacheDir(DefaultNacosCacheDir),
constant.WithNotLoadCacheAtStart(DefaultNacosNotLoadCache),
constant.WithLogRollingConfig(&constant.ClientLogRollingConfig{
MaxAge: DefaultNacosLogMaxAge,
}),
constant.WithUpdateCacheWhenEmpty(w.updateCacheWhenEmpty),
constant.WithNamespaceId(w.NacosNamespaceId),
constant.WithAccessKey(w.NacosAccessKey),
constant.WithSecretKey(w.NacosSecretKey),
)
initTimer := time.NewTimer(DefaultInitTimeout)
w.serverConfig = []constant.ServerConfig{
*constant.NewServerConfig(w.Domain, uint64(w.Port)),
}
success := make(chan struct{})
go func() {
configClient, err := clients.NewConfigClient(vo.NacosClientParam{
ClientConfig: w.nacosClientConfig,
ServerConfigs: w.serverConfig,
})
if err == nil {
w.configClient = configClient
close(success)
} else {
mcpServerLog.Errorf("can not create naming client, err:%v", err)
}
}()
select {
case <-initTimer.C:
return nil, errors.New("new nacos mcp server watcher timeout")
case <-success:
return w, nil
}
}
func WithNacosAddressServer(nacosAddressServer string) WatcherOption {
return func(w *watcher) {
w.NacosAddressServer = nacosAddressServer
}
}
func WithNacosAccessKey(nacosAccessKey string) WatcherOption {
return func(w *watcher) {
w.NacosAccessKey = nacosAccessKey
}
}
func WithNacosSecretKey(nacosSecretKey string) WatcherOption {
return func(w *watcher) {
w.NacosSecretKey = nacosSecretKey
}
}
func WithNacosNamespaceId(nacosNamespaceId string) WatcherOption {
return func(w *watcher) {
if nacosNamespaceId == "" {
w.NacosNamespaceId = "nacos-default-mcp"
} else {
w.NacosNamespaceId = nacosNamespaceId
}
}
}
func WithNacosNamespace(nacosNamespace string) WatcherOption {
return func(w *watcher) {
w.NacosNamespace = nacosNamespace
}
}
func WithNacosGroups(nacosGroups []string) WatcherOption {
return func(w *watcher) {
if len(nacosGroups) == 0 {
w.NacosGroups = []string{"mcp-server"}
} else {
w.NacosGroups = nacosGroups
}
}
}
func WithNacosRefreshInterval(refreshInterval int64) WatcherOption {
return func(w *watcher) {
if refreshInterval < int64(DefaultRefreshIntervalLimit) {
refreshInterval = int64(DefaultRefreshIntervalLimit)
}
w.NacosRefreshInterval = refreshInterval
}
}
func WithType(t string) WatcherOption {
return func(w *watcher) {
w.Type = t
}
}
func WithName(name string) WatcherOption {
return func(w *watcher) {
w.Name = name
}
}
func WithDomain(domain string) WatcherOption {
return func(w *watcher) {
w.Domain = domain
}
}
func WithPort(port uint32) WatcherOption {
return func(w *watcher) {
w.Port = port
}
}
func WithMcpExportDomains(exportDomains []string) WatcherOption {
return func(w *watcher) {
w.McpServerExportDomains = exportDomains
}
}
func WithMcpBaseUrl(url string) WatcherOption {
return func(w *watcher) {
w.McpServerBaseUrl = url
}
}
func WithEnableMcpServer(enable *wrappers.BoolValue) WatcherOption {
return func(w *watcher) {
w.EnableMCPServer = enable
}
}
func WithNamespace(ns string) WatcherOption {
return func(w *watcher) {
w.namespace = ns
}
}
func WithClusterId(id string) WatcherOption {
return func(w *watcher) {
w.clusterId = id
}
}
func (w *watcher) Run() {
ticker := time.NewTicker(time.Duration(w.NacosRefreshInterval))
defer ticker.Stop()
w.Status = provider.ProbeWatcherStatus(w.Domain, strconv.FormatUint(uint64(w.Port), 10))
err := w.fetchAllMcpConfig()
if err != nil {
mcpServerLog.Errorf("first fetch mcp server config failed, err:%v", err)
} else {
w.Ready(true)
}
for {
select {
case <-ticker.C:
err := w.fetchAllMcpConfig()
if err != nil {
mcpServerLog.Errorf("fetch mcp server config failed, err:%v", err)
} else {
w.Ready(true)
}
case <-w.stop:
return
}
}
}
func (w *watcher) fetchAllMcpConfig() error {
w.mutex.Lock()
defer w.mutex.Unlock()
if w.isStop {
return nil
}
fetchedConfigs := make(map[string]bool)
var tries int
isV3 := true
if w.EnableMCPServer != nil {
isV3 = w.EnableMCPServer.GetValue()
}
for _, groupName := range w.NacosGroups {
for page := 1; ; page++ {
ss, err := w.configClient.SearchConfig(vo.SearchConfigParam{
Group: groupName,
Search: "blur",
PageNo: page,
PageSize: DefaultFetchPageSize,
IsV3: isV3,
})
if err != nil {
if tries > 10 {
return err
}
mcpServerLog.Errorf("fetch nacos config list failed, err:%v, pageNo:%d", err, page)
page--
tries++
continue
}
for _, item := range ss.PageItems {
fetchedConfigs[groupName+DefaultJoiner+item.DataId] = true
}
if len(ss.PageItems) < DefaultFetchPageSize {
break
}
}
}
for key := range w.watchingConfig {
if _, exist := fetchedConfigs[key]; !exist {
s := strings.Split(key, DefaultJoiner)
err := w.unsubscribe(s[0], s[1])
if err != nil {
return err
}
delete(w.watchingConfig, key)
}
}
wg := sync.WaitGroup{}
subscribeFailed := atomic.NewBool(false)
watchingKeys := make(chan string, len(fetchedConfigs))
for key := range fetchedConfigs {
s := strings.Split(key, DefaultJoiner)
if _, exist := w.watchingConfig[key]; !exist {
wg.Add(1)
go func(k string) {
err := w.subscribe(s[0], s[1])
if err != nil {
subscribeFailed.Store(true)
mcpServerLog.Errorf("subscribe failed, group: %v, service: %v, errors: %v", s[0], s[1], err)
} else {
watchingKeys <- k
}
wg.Done()
}(key)
}
}
wg.Wait()
close(watchingKeys)
for key := range watchingKeys {
w.watchingConfig[key] = true
}
if subscribeFailed.Load() {
return errors.New("subscribe services failed")
}
return nil
}
func (w *watcher) unsubscribe(groupName string, dataId string) error {
mcpServerLog.Infof("unsubscribe mcp server, groupName:%s, dataId:%s", groupName, dataId)
defer w.UpdateService()
err := w.configClient.CancelListenConfig(vo.ConfigParam{
DataId: dataId,
Group: groupName,
})
if err != nil {
mcpServerLog.Errorf("unsubscribe mcp server error:%v, groupName:%s, dataId:%s", err, groupName, dataId)
return err
}
key := strings.Join([]string{w.Name, w.NacosNamespace, groupName, dataId}, DefaultJoiner)
w.configToConfigListener[key].Stop()
delete(w.watchingConfigRefs, key)
delete(w.configToConfigListener, key)
// remove service for this config
configKey := strings.Join([]string{groupName, dataId}, DefaultJoiner)
svcInfo := w.configToService[configKey]
split := strings.Split(svcInfo, DefaultJoiner)
svcNamespace := split[0]
svcGroup := split[1]
svcName := split[2]
if w.serviceCache[svcNamespace] != nil {
err = w.serviceCache[svcNamespace].RemoveListener(svcGroup, svcName, configKey)
if err != nil {
mcpServerLog.Errorf("remove service listener error:%v, groupName:%s, dataId:%s", err, groupName, dataId)
}
}
delete(w.configToService, configKey)
w.cache.UpdateConfigCache(config.GroupVersionKind{}, key, nil, true)
return nil
}
func (w *watcher) subscribe(groupName string, dataId string) error {
mcpServerLog.Infof("subscribe mcp server, groupName:%s, dataId:%s", groupName, dataId)
// first we get this config and callback manually
content, err := w.configClient.GetConfig(vo.ConfigParam{
DataId: dataId,
Group: groupName,
})
if err != nil {
mcpServerLog.Errorf("get config %s/%s err: %v", groupName, dataId, err)
} else {
w.getConfigCallback(w.NacosNamespace, groupName, dataId, content)
}
// second, we set callback for this config
err = w.configClient.ListenConfig(vo.ConfigParam{
DataId: dataId,
Group: groupName,
OnChange: w.getConfigCallback,
})
if err != nil {
mcpServerLog.Errorf("subscribe mcp server error:%v, groupName:%s, dataId:%s", err, groupName, dataId)
return err
}
return nil
}
func (w *watcher) getConfigCallback(namespace, group, dataId, data string) {
mcpServerLog.Infof("get config callback, namespace:%s, groupName:%s, dataId:%s", namespace, group, dataId)
if data == "" {
return
}
key := strings.Join([]string{w.Name, w.NacosNamespace, group, dataId}, DefaultJoiner)
routeName := fmt.Sprintf("%s-%s-%s", provider.IstioMcpAutoGeneratedHttpRouteName, group, strings.TrimSuffix(dataId, ".json"))
mcpServer := &provider.McpServer{}
if err := json.Unmarshal([]byte(data), mcpServer); err != nil {
mcpServerLog.Errorf("Unmarshal config data to mcp server error:%v, namespace:%s, groupName:%s, dataId:%s", err, namespace, group, dataId)
return
}
if mcpServer.Protocol == provider.StdioProtocol || mcpServer.Protocol == provider.DubboProtocol || mcpServer.Protocol == provider.McpSSEProtocol {
return
}
// process mcp service
w.subMutex.Lock()
defer w.subMutex.Unlock()
if err := w.buildServiceEntryForMcpServer(mcpServer, group, dataId); err != nil {
mcpServerLog.Errorf("build service entry for mcp server failed, namespace %v, group: %v, dataId %v, errors: %v", namespace, group, dataId, err)
}
// process mcp wasm
// only generate wasm plugin for http protocol mcp server
if mcpServer.Protocol != provider.HttpProtocol {
return
}
if _, exist := w.configToConfigListener[key]; !exist {
w.configToConfigListener[key] = NewMultiConfigListener(w.configClient, w.multiCallback(mcpServer, routeName, key))
}
if _, exist := w.watchingConfigRefs[key]; !exist {
w.watchingConfigRefs[key] = sets.New[string]()
}
listener := w.configToConfigListener[key]
curRef := sets.Set[string]{}
// add description ref
curRef.Insert(strings.Join([]string{provider.DefaultMcpToolsGroup, mcpServer.ToolsDescriptionRef}, DefaultJoiner))
// add credential ref
credentialNameMap := map[string]string{}
for name, ref := range mcpServer.Credentials {
credKey := strings.Join([]string{provider.DefaultMcpCredentialsGroup, ref.Ref}, DefaultJoiner)
curRef.Insert(credKey)
credentialNameMap[credKey] = name
}
w.callbackMutex.Lock()
w.credentialKeyToName[key] = credentialNameMap
w.callbackMutex.Unlock()
toBeAdd := curRef.Difference(w.watchingConfigRefs[key])
toBeDelete := w.watchingConfigRefs[key].Difference(curRef)
var toBeListen, toBeUnListen []vo.ConfigParam
for item, _ := range toBeAdd {
split := strings.Split(item, DefaultJoiner)
toBeListen = append(toBeListen, vo.ConfigParam{
Group: split[0],
DataId: split[1],
})
}
for item, _ := range toBeDelete {
split := strings.Split(item, DefaultJoiner)
toBeUnListen = append(toBeUnListen, vo.ConfigParam{
Group: split[0],
DataId: split[1],
})
}
// listen description and credential config
if len(toBeListen) > 0 {
if err := listener.StartListen(toBeListen); err != nil {
mcpServerLog.Errorf("listen config ref failed, group: %v, dataId %v, errors: %v", group, dataId, err)
}
}
// cancel listen description and credential config
if len(toBeUnListen) > 0 {
if err := listener.CancelListen(toBeUnListen); err != nil {
mcpServerLog.Errorf("cancel listen config ref failed, group: %v, dataId %v, errors: %v", group, dataId, err)
}
}
}
func (w *watcher) multiCallback(server *provider.McpServer, routeName, configKey string) func(map[string]string) {
callback := func(configs map[string]string) {
defer w.UpdateService()
mcpServerLog.Infof("callback, ref config changed: %s", configKey)
rule := &provider.McpServerRule{
MatchRoute: []string{routeName},
Server: &provider.ServerConfig{
Name: server.Name,
Config: map[string]interface{}{},
},
}
// process mcp credential
credentialConfig := map[string]interface{}{}
for key, data := range configs {
if strings.HasPrefix(key, provider.DefaultMcpToolsGroup) {
// skip mcp tool description
continue
}
var cred interface{}
if err := json.Unmarshal([]byte(data), &cred); err != nil {
mcpServerLog.Errorf("unmarshal credential data %v to map error:%v", key, err)
}
w.callbackMutex.Lock()
name := w.credentialKeyToName[configKey][key]
w.callbackMutex.Unlock()
credentialConfig[name] = cred
}
rule.Server.Config["credentials"] = credentialConfig
// process mcp tool description
var allowTools []string
for key, toolData := range configs {
if strings.HasPrefix(key, provider.DefaultMcpCredentialsGroup) {
// skip mcp credentials
continue
}
toolsDescription := &provider.McpToolConfig{}
if err := json.Unmarshal([]byte(toolData), toolsDescription); err != nil {
mcpServerLog.Errorf("unmarshal toolsDescriptionRef to mcp tool config error:%v", err)
}
for _, t := range toolsDescription.Tools {
convertTool := &provider.McpTool{Name: t.Name, Description: t.Description}
toolMeta := toolsDescription.ToolsMeta[t.Name]
if toolMeta != nil && toolMeta.Enabled {
allowTools = append(allowTools, t.Name)
}
argsPosition, err := getArgsPositionFromToolMeta(toolMeta)
if err != nil {
mcpServerLog.Errorf("get args position from tool meta error:%v, tool name %v", err, t.Name)
}
requiredMap := sets.Set[string]{}
for _, s := range t.InputSchema.Required {
requiredMap.Insert(s)
}
for argsName, args := range t.InputSchema.Properties {
convertArgs, err := parseMcpArgs(args)
if err != nil {
mcpServerLog.Errorf("parse mcp args error:%v, tool name %v, args name %v", err, t.Name, argsName)
continue
}
convertArgs.Name = argsName
convertArgs.Required = requiredMap.Contains(argsName)
if pos, exist := argsPosition[argsName]; exist {
convertArgs.Position = pos
}
convertTool.Args = append(convertTool.Args, convertArgs)
mcpServerLog.Debugf("parseMcpArgs, toolArgs:%v", convertArgs)
}
requestTemplate, err := getRequestTemplateFromToolMeta(toolMeta)
if err != nil {
mcpServerLog.Errorf("get request template from tool meta error:%v, tool name %v", err, t.Name)
} else {
convertTool.RequestTemplate = requestTemplate
}
responseTemplate, err := getResponseTemplateFromToolMeta(toolMeta)
if err != nil {
mcpServerLog.Errorf("get response template from tool meta error:%v, tool name %v", err, t.Name)
} else {
convertTool.ResponseTemplate = responseTemplate
}
rule.Tools = append(rule.Tools, convertTool)
}
}
rule.Server.AllowTools = allowTools
wasmPluginConfig := &config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.WasmPlugin,
Namespace: w.namespace,
},
Spec: rule,
}
w.cache.UpdateConfigCache(gvk.WasmPlugin, configKey, wasmPluginConfig, false)
}
return callback
}
func (w *watcher) buildServiceEntryForMcpServer(mcpServer *provider.McpServer, configGroup, dataId string) error {
if mcpServer == nil || mcpServer.RemoteServerConfig == nil || mcpServer.RemoteServerConfig.ServiceRef == nil {
return nil
}
mcpServerLog.Debugf("ServiceRef %v for %v", mcpServer.RemoteServerConfig.ServiceRef, dataId)
configKey := strings.Join([]string{configGroup, dataId}, DefaultJoiner)
serviceGroup := mcpServer.RemoteServerConfig.ServiceRef.GroupName
serviceNamespace := mcpServer.RemoteServerConfig.ServiceRef.NamespaceId
serviceName := mcpServer.RemoteServerConfig.ServiceRef.ServiceName
if serviceNamespace == "" {
serviceNamespace = provider.DefaultNacosServiceNamespace
}
// update config to service and unsubscribe old service
curSvcKey := strings.Join([]string{serviceNamespace, serviceGroup, serviceName}, DefaultJoiner)
if svcKey, exist := w.configToService[configKey]; exist && svcKey != curSvcKey {
split := strings.Split(svcKey, DefaultJoiner)
if svcCache, has := w.serviceCache[split[0]]; has {
if err := svcCache.RemoveListener(split[1], split[2], configKey); err != nil {
mcpServerLog.Errorf("remove listener error:%v", err)
}
}
}
w.configToService[configKey] = curSvcKey
if _, exist := w.serviceCache[serviceNamespace]; !exist {
namingConfig := constant.NewClientConfig(
constant.WithTimeoutMs(DefaultNacosTimeout),
constant.WithLogLevel(DefaultNacosLogLevel),
constant.WithLogDir(DefaultNacosLogDir),
constant.WithCacheDir(DefaultNacosCacheDir),
constant.WithNotLoadCacheAtStart(DefaultNacosNotLoadCache),
constant.WithLogRollingConfig(&constant.ClientLogRollingConfig{
MaxAge: DefaultNacosLogMaxAge,
}),
constant.WithUpdateCacheWhenEmpty(w.updateCacheWhenEmpty),
constant.WithNamespaceId(serviceNamespace),
constant.WithAccessKey(w.NacosAccessKey),
constant.WithSecretKey(w.NacosSecretKey),
)
client, err := clients.NewNamingClient(vo.NacosClientParam{
ClientConfig: namingConfig,
ServerConfigs: w.serverConfig,
})
if err == nil {
w.serviceCache[serviceNamespace] = NewServiceCache(client)
} else {
return fmt.Errorf("can not create naming client err:%v", err)
}
}
svcCache := w.serviceCache[serviceNamespace]
err := svcCache.AddListener(serviceGroup, serviceName, configKey, w.getServiceCallback(mcpServer, configGroup, dataId))
if err != nil {
return fmt.Errorf("add listener for dataId %v, service %s/%s error:%v", dataId, serviceGroup, serviceName, err)
}
return nil
}
func (w *watcher) getServiceCallback(server *provider.McpServer, configGroup, dataId string) func(services []model.Instance) {
groupName := server.RemoteServerConfig.ServiceRef.GroupName
if groupName == "DEFAULT_GROUP" {
groupName = "DEFAULT-GROUP"
}
namespace := server.RemoteServerConfig.ServiceRef.NamespaceId
serviceName := server.RemoteServerConfig.ServiceRef.ServiceName
path := server.RemoteServerConfig.ExportPath
protocol := server.Protocol
return func(services []model.Instance) {
defer w.UpdateService()
configKey := strings.Join([]string{w.Name, w.NacosNamespace, configGroup, dataId}, DefaultJoiner)
host := getNacosServiceFullHost(groupName, namespace, serviceName)
mcpServerLog.Infof("callback for %s/%s, serviceName : %s", configGroup, dataId, host)
serviceEntry := w.generateServiceEntry(host, services)
se := &config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.ServiceEntry,
Name: fmt.Sprintf("%s-%s-%s", provider.IstioMcpAutoGeneratedSeName, configGroup, strings.TrimSuffix(dataId, ".json")),
Namespace: w.namespace,
},
Spec: serviceEntry,
}
if protocol == provider.McpSSEProtocol {
destinationRule := w.generateDrForSSEService(host)
dr := &config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.DestinationRule,
Name: fmt.Sprintf("%s-%s-%s", provider.IstioMcpAutoGeneratedDrName, configGroup, strings.TrimSuffix(dataId, ".json")),
Namespace: w.namespace,
},
Spec: destinationRule,
}
w.cache.UpdateConfigCache(gvk.DestinationRule, configKey, dr, false)
}
w.cache.UpdateConfigCache(gvk.ServiceEntry, configKey, se, false)
vs := w.buildVirtualServiceForMcpServer(serviceEntry, configGroup, dataId, path, server.Name)
w.cache.UpdateConfigCache(gvk.VirtualService, configKey, vs, false)
}
}
func (w *watcher) buildVirtualServiceForMcpServer(serviceentry *v1alpha3.ServiceEntry, group, dataId, path, serverName string) *config.Config {
if serviceentry == nil {
return nil
}
hosts := w.McpServerExportDomains
if len(hosts) == 0 {
hosts = []string{"*"}
}
var gateways []string
for _, host := range hosts {
cleanHost := common2.CleanHost(host)
// namespace/name, name format: (istio cluster id)-host
gateways = append(gateways, w.namespace+"/"+
common2.CreateConvertedName(w.clusterId, cleanHost),
common2.CreateConvertedName(constants.IstioIngressGatewayName, cleanHost))
}
routeName := fmt.Sprintf("%s-%s-%s", provider.IstioMcpAutoGeneratedHttpRouteName, group, strings.TrimSuffix(dataId, ".json"))
mergePath := "/" + serverName
if w.McpServerBaseUrl != "/" {
mergePath = strings.TrimSuffix(w.McpServerBaseUrl, "/") + mergePath
}
if path != "/" {
mergePath = mergePath + "/" + strings.TrimPrefix(path, "/")
}
vs := &v1alpha3.VirtualService{
Hosts: hosts,
Gateways: gateways,
Http: []*v1alpha3.HTTPRoute{{
Name: routeName,
Match: []*v1alpha3.HTTPMatchRequest{{
Uri: &v1alpha3.StringMatch{
MatchType: &v1alpha3.StringMatch_Prefix{
Prefix: mergePath,
},
},
}},
Rewrite: &v1alpha3.HTTPRewrite{
Uri: path,
},
Route: []*v1alpha3.HTTPRouteDestination{{
Destination: &v1alpha3.Destination{
Host: serviceentry.Hosts[0],
Port: &v1alpha3.PortSelector{
Number: serviceentry.Ports[0].Number,
},
},
}},
}},
}
mcpServerLog.Debugf("construct virtualservice %v", vs)
return &config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.VirtualService,
Name: fmt.Sprintf("%s-%s-%s", provider.IstioMcpAutoGeneratedVsName, group, dataId),
Namespace: w.namespace,
},
Spec: vs,
}
}
func (w *watcher) generateServiceEntry(host string, services []model.Instance) *v1alpha3.ServiceEntry {
portList := make([]*v1alpha3.ServicePort, 0)
endpoints := make([]*v1alpha3.WorkloadEntry, 0)
isDnsService := false
for _, service := range services {
protocol := common.HTTP
if service.Metadata != nil && service.Metadata["protocol"] != "" {
protocol = common.ParseProtocol(service.Metadata["protocol"])
}
port := &v1alpha3.ServicePort{
Name: protocol.String(),
Number: uint32(service.Port),
Protocol: protocol.String(),
}
if len(portList) == 0 {
portList = append(portList, port)
}
if !isValidIP(service.Ip) {
isDnsService = true
}
endpoint := &v1alpha3.WorkloadEntry{
Address: service.Ip,
Ports: map[string]uint32{port.Protocol: port.Number},
Labels: service.Metadata,
}
endpoints = append(endpoints, endpoint)
}
resolution := v1alpha3.ServiceEntry_STATIC
if isDnsService {
resolution = v1alpha3.ServiceEntry_DNS
}
se := &v1alpha3.ServiceEntry{
Hosts: []string{host},
Ports: portList,
Location: v1alpha3.ServiceEntry_MESH_INTERNAL,
Resolution: resolution,
Endpoints: endpoints,
}
return se
}
func (w *watcher) generateDrForSSEService(host string) *v1alpha3.DestinationRule {
dr := &v1alpha3.DestinationRule{
Host: host,
TrafficPolicy: &v1alpha3.TrafficPolicy{
LoadBalancer: &v1alpha3.LoadBalancerSettings{
LbPolicy: &v1alpha3.LoadBalancerSettings_ConsistentHash{
ConsistentHash: &v1alpha3.LoadBalancerSettings_ConsistentHashLB{
HashKey: &v1alpha3.LoadBalancerSettings_ConsistentHashLB_UseSourceIp{
UseSourceIp: true,
},
},
},
},
},
}
return dr
}
func parseMcpArgs(args interface{}) (*provider.ToolArgs, error) {
argsData, err := json.Marshal(args)
if err != nil {
return nil, err
}
toolArgs := &provider.ToolArgs{}
if err = json.Unmarshal(argsData, toolArgs); err != nil {
return nil, err
}
return toolArgs, nil
}
func getArgsPositionFromToolMeta(toolMeta *provider.ToolsMeta) (map[string]string, error) {
result := map[string]string{}
if toolMeta == nil {
return result, nil
}
toolTemplate := toolMeta.Templates
for kind, meta := range toolTemplate {
switch kind {
case provider.JsonGoTemplateType:
templateData, err := json.Marshal(meta)
if err != nil {
return result, err
}
template := &provider.JsonGoTemplate{}
if err = json.Unmarshal(templateData, template); err != nil {
return result, err
}
result = mergeMaps(result, template.ArgsPosition)
default:
return result, fmt.Errorf("unsupport tool meta type %v", kind)
}
}
return result, nil
}
func getRequestTemplateFromToolMeta(toolMeta *provider.ToolsMeta) (*provider.RequestTemplate, error) {
if toolMeta == nil {
return nil, nil
}
toolTemplate := toolMeta.Templates
for kind, meta := range toolTemplate {
switch kind {
case provider.JsonGoTemplateType:
templateData, err := json.Marshal(meta)
if err != nil {
return nil, err
}
template := &provider.JsonGoTemplate{}
if err = json.Unmarshal(templateData, template); err != nil {
return nil, err
}
return &template.RequestTemplate, nil
default:
return nil, fmt.Errorf("unsupport tool meta type")
}
}
return nil, nil
}
func getResponseTemplateFromToolMeta(toolMeta *provider.ToolsMeta) (*provider.ResponseTemplate, error) {
if toolMeta == nil {
return nil, nil
}
toolTemplate := toolMeta.Templates
for kind, meta := range toolTemplate {
switch kind {
case provider.JsonGoTemplateType:
templateData, err := json.Marshal(meta)
if err != nil {
return nil, err
}
template := &provider.JsonGoTemplate{}
if err = json.Unmarshal(templateData, template); err != nil {
return nil, err
}
return &template.ResponseTemplate, nil
default:
return nil, fmt.Errorf("unsupport tool meta type")
}
}
return nil, nil
}
func mergeMaps(maps ...map[string]string) map[string]string {
if len(maps) == 0 {
return nil
}
res := make(map[string]string, len(maps[0]))
for _, m := range maps {
for k, v := range m {
res[k] = v
}
}
return res
}
func getNacosServiceFullHost(groupName, namespace, serviceName string) string {
suffix := strings.Join([]string{groupName, namespace, string(provider.Nacos)}, common.DotSeparator)
host := strings.Join([]string{serviceName, suffix}, common.DotSeparator)
return host
}
func (w *watcher) Stop() {
w.mutex.Lock()
defer w.mutex.Unlock()
mcpServerLog.Infof("unsubscribe all configs")
for key := range w.watchingConfig {
s := strings.Split(key, DefaultJoiner)
err := w.unsubscribe(s[0], s[1])
if err == nil {
delete(w.watchingConfig, key)
}
}
mcpServerLog.Infof("stop all service nameing client")
for _, client := range w.serviceCache {
client.Stop()
}
w.isStop = true
mcpServerLog.Infof("stop all config client")
mcpServerLog.Infof("watcher %v stop", w.Name)
close(w.stop)
w.Ready(false)
}
func (w *watcher) IsHealthy() bool {
return w.Status == provider.Healthy
}
func (w *watcher) GetRegistryType() string {
return w.RegistryType.String()
}
func isValidIP(ipStr string) bool {
ip := net.ParseIP(ipStr)
return ip != nil
}

View File

@@ -23,6 +23,7 @@ import (
"sync"
"time"
"github.com/alibaba/higress/registry/nacos/mcpserver"
"istio.io/pkg/log"
apiv1 "github.com/alibaba/higress/api/networking/v1"
@@ -50,9 +51,10 @@ type Reconciler struct {
serviceUpdate func()
client kube.Client
namespace string
clusterId string
}
func NewReconciler(serviceUpdate func(), client kube.Client, namespace string) *Reconciler {
func NewReconciler(serviceUpdate func(), client kube.Client, namespace, clusterId string) *Reconciler {
return &Reconciler{
Cache: memory.NewCache(),
registries: make(map[string]*apiv1.RegistryConfig),
@@ -60,6 +62,7 @@ func NewReconciler(serviceUpdate func(), client kube.Client, namespace string) *
serviceUpdate: serviceUpdate,
client: client,
namespace: namespace,
clusterId: clusterId,
}
}
@@ -183,6 +186,26 @@ func (r *Reconciler) generateWatcherFromRegistryConfig(registry *apiv1.RegistryC
nacosv2.WithNacosRefreshInterval(registry.NacosRefreshInterval),
nacosv2.WithAuthOption(authOption),
)
case string(Nacos3):
watcher, err = mcpserver.NewWatcher(
r.Cache,
mcpserver.WithType(registry.Type),
mcpserver.WithName(registry.Name),
mcpserver.WithNacosAddressServer(registry.NacosAddressServer),
mcpserver.WithDomain(registry.Domain),
mcpserver.WithPort(registry.Port),
mcpserver.WithNacosAccessKey(registry.NacosAccessKey),
mcpserver.WithNacosSecretKey(registry.NacosSecretKey),
mcpserver.WithNacosNamespaceId(registry.NacosNamespaceId),
mcpserver.WithNacosNamespace(registry.NacosNamespace),
mcpserver.WithNacosGroups(registry.NacosGroups),
mcpserver.WithNacosRefreshInterval(registry.NacosRefreshInterval),
mcpserver.WithMcpExportDomains(registry.McpServerExportDomains),
mcpserver.WithMcpBaseUrl(registry.McpServerBaseUrl),
mcpserver.WithEnableMcpServer(registry.EnableMCPServer),
mcpserver.WithClusterId(r.clusterId),
mcpserver.WithNamespace(r.namespace),
)
case string(Zookeeper):
watcher, err = zookeeper.NewWatcher(
r.Cache,

View File

@@ -25,6 +25,7 @@ const (
Consul ServiceRegistryType = "consul"
Nacos ServiceRegistryType = "nacos"
Nacos2 ServiceRegistryType = "nacos2"
Nacos3 ServiceRegistryType = "nacos3"
Static ServiceRegistryType = "static"
DNS ServiceRegistryType = "dns"
Healthy WatcherStatus = "healthy"

View File

@@ -16,24 +16,16 @@
set -euo pipefail
INNER_GO_FILTER_NAME=${GO_FILTER_NAME-""}
OUTPUT_PACKAGE_DIR=${OUTPUT_PACKAGE_DIR:-"../../external/package/"}
cd ./plugins/golang-filter
if [ ! -n "$INNER_GO_FILTER_NAME" ]; then
GO_FILTERS_DIR=$(pwd)
echo "🚀 Build all Go Filters under folder of $GO_FILTERS_DIR"
for file in `ls $GO_FILTERS_DIR`
do
if [ -d $GO_FILTERS_DIR/$file ]; then
name=${file##*/}
echo "🚀 Build Go Filter: $name"
GO_FILTER_NAME=${name} GOARCH=${TARGET_ARCH} make build
cp ${GO_FILTERS_DIR}/${file}/golang-filter_${TARGET_ARCH}.so ${OUTPUT_PACKAGE_DIR}
fi
done
else
echo "🚀 Build Go Filter: $INNER_GO_FILTER_NAME"
GO_FILTER_NAME=${INNER_GO_FILTER_NAME} make build
fi
cd plugins/golang-filter
GO_FILTERS_DIR=$(pwd)
echo "🚀 Build Go Filter"
GOARCH=${TARGET_ARCH} make build
cp ${GO_FILTERS_DIR}/golang-filter_${TARGET_ARCH}.so ${OUTPUT_PACKAGE_DIR}