mirror of
https://github.com/alibaba/higress.git
synced 2026-06-06 03:07:28 +08:00
feat: Refactor mcpServer.matchList config generation logic (#2207)
This commit is contained in:
@@ -22,6 +22,7 @@ const (
|
||||
IstioMcpAutoGeneratedSeName = IstioMcpAutoGeneratedPrefix + "-se"
|
||||
IstioMcpAutoGeneratedDrName = IstioMcpAutoGeneratedPrefix + "-dr"
|
||||
IstioMcpAutoGeneratedHttpRouteName = IstioMcpAutoGeneratedPrefix + "-httproute"
|
||||
IstioMcpAutoGeneratedMcpServerName = IstioMcpAutoGeneratedPrefix + "-mcpserver"
|
||||
|
||||
DefaultMcpToolsGroup = "mcp-tools"
|
||||
DefaultMcpCredentialsGroup = "credentials"
|
||||
|
||||
@@ -317,7 +317,7 @@ func (s *store) GetAllDestinationRuleWrapper() []*ingress.WrapperDestinationRule
|
||||
dr := cfg.Spec.(*v1alpha3.DestinationRule)
|
||||
drwList = append(drwList, &ingress.WrapperDestinationRule{
|
||||
DestinationRule: dr,
|
||||
ServiceKey: ingress.ServiceKey{ServiceFQDN: dr.Host},
|
||||
ServiceKey: ingress.ServiceKey{Namespace: "mcp", Name: dr.Host, ServiceFQDN: dr.Host},
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
apiv1 "github.com/alibaba/higress/api/networking/v1"
|
||||
"github.com/alibaba/higress/pkg/common"
|
||||
common2 "github.com/alibaba/higress/pkg/ingress/kube/common"
|
||||
"github.com/alibaba/higress/pkg/ingress/kube/mcpserver"
|
||||
provider "github.com/alibaba/higress/registry"
|
||||
"github.com/alibaba/higress/registry/memory"
|
||||
"github.com/golang/protobuf/ptypes/wrappers"
|
||||
@@ -56,7 +57,26 @@ const (
|
||||
DefaultRefreshIntervalLimit = time.Second * 10
|
||||
DefaultFetchPageSize = 50
|
||||
DefaultJoiner = "@@"
|
||||
NacosV3LabelKey = "isV3"
|
||||
)
|
||||
|
||||
var (
|
||||
supportedProtocols = map[string]bool{
|
||||
provider.HttpProtocol: true,
|
||||
provider.McpSSEProtocol: true,
|
||||
provider.McpStreambleProtocol: true,
|
||||
}
|
||||
protocolUpstreamTypeMapping = map[string]string{
|
||||
provider.HttpProtocol: mcpserver.UpstreamTypeRest,
|
||||
provider.McpSSEProtocol: mcpserver.UpstreamTypeSSE,
|
||||
provider.McpStreambleProtocol: mcpserver.UpstreamTypeStreamable,
|
||||
}
|
||||
routeRewriteProtocols = map[string]bool{
|
||||
provider.McpSSEProtocol: true,
|
||||
provider.McpStreambleProtocol: true,
|
||||
}
|
||||
mcpServerRewriteProtocols = map[string]bool{
|
||||
provider.McpSSEProtocol: true,
|
||||
}
|
||||
)
|
||||
|
||||
var mcpServerLog = log.RegisterScope("McpServer", "Nacos Mcp Server Watcher process.")
|
||||
@@ -431,7 +451,7 @@ func (w *watcher) getConfigCallback(namespace, group, dataId, data string) {
|
||||
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 {
|
||||
if !supportedProtocols[mcpServer.Protocol] {
|
||||
return
|
||||
}
|
||||
// process mcp service
|
||||
@@ -670,7 +690,9 @@ func (w *watcher) getServiceCallback(server *provider.McpServer, configGroup, da
|
||||
}
|
||||
namespace := server.RemoteServerConfig.ServiceRef.NamespaceId
|
||||
serviceName := server.RemoteServerConfig.ServiceRef.ServiceName
|
||||
path := server.RemoteServerConfig.ExportPath
|
||||
// Higress doesn't care about the MCP export path configured in nacos.
|
||||
// Any path of the mcp server are supported in request routing.
|
||||
path := "/"
|
||||
protocol := server.Protocol
|
||||
host := getNacosServiceFullHost(groupName, namespace, serviceName)
|
||||
|
||||
@@ -708,6 +730,8 @@ func (w *watcher) getServiceCallback(server *provider.McpServer, configGroup, da
|
||||
w.cache.UpdateConfigCache(gvk.ServiceEntry, configKey, se, false)
|
||||
vs := w.buildVirtualServiceForMcpServer(serviceEntry, configGroup, dataId, path, server)
|
||||
w.cache.UpdateConfigCache(gvk.VirtualService, configKey, vs, false)
|
||||
mcpServer := w.buildMcpServerForMcpServer(vs.Spec.(*v1alpha3.VirtualService), configGroup, dataId, path, server)
|
||||
w.cache.UpdateConfigCache(mcpserver.GvkMcpServer, configKey, mcpServer, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -735,16 +759,28 @@ func (w *watcher) buildVirtualServiceForMcpServer(serviceentry *v1alpha3.Service
|
||||
if path != "/" {
|
||||
mergePath = mergePath + "/" + strings.TrimPrefix(path, "/")
|
||||
}
|
||||
mergePath = strings.TrimSuffix(mergePath, "/")
|
||||
|
||||
vs := &v1alpha3.VirtualService{
|
||||
Hosts: hosts,
|
||||
Gateways: gateways,
|
||||
Http: []*v1alpha3.HTTPRoute{{
|
||||
Name: routeName,
|
||||
// We need to use both exact and prefix matches here to ensure a proper matching.
|
||||
// Also otherwise, prefix rewrite won't work correctly for Streamable HTTP transport, either.
|
||||
// Example:
|
||||
// Assume mergePath=/mcp/test prefixRewrite=/ requestPath=/mcp/test/abc
|
||||
// If we only use prefix match, the rewritten path will be //abc.
|
||||
Match: []*v1alpha3.HTTPMatchRequest{{
|
||||
Uri: &v1alpha3.StringMatch{
|
||||
MatchType: &v1alpha3.StringMatch_Exact{
|
||||
Exact: mergePath,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Uri: &v1alpha3.StringMatch{
|
||||
MatchType: &v1alpha3.StringMatch_Prefix{
|
||||
Prefix: mergePath,
|
||||
Prefix: mergePath + "/",
|
||||
},
|
||||
},
|
||||
}},
|
||||
@@ -759,9 +795,9 @@ func (w *watcher) buildVirtualServiceForMcpServer(serviceentry *v1alpha3.Service
|
||||
}},
|
||||
}
|
||||
|
||||
if server.Protocol == provider.McpStreambleProtocol {
|
||||
if routeRewriteProtocols[server.Protocol] {
|
||||
vs.Http[0].Rewrite = &v1alpha3.HTTPRewrite{
|
||||
Uri: path,
|
||||
Uri: "/",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -777,6 +813,49 @@ func (w *watcher) buildVirtualServiceForMcpServer(serviceentry *v1alpha3.Service
|
||||
}
|
||||
}
|
||||
|
||||
func (w *watcher) buildMcpServerForMcpServer(vs *v1alpha3.VirtualService, group, dataId, path string, server *provider.McpServer) *config.Config {
|
||||
if vs == nil {
|
||||
return nil
|
||||
}
|
||||
domains := w.McpServerExportDomains
|
||||
if len(domains) == 0 {
|
||||
domains = []string{"*"}
|
||||
}
|
||||
name := fmt.Sprintf("%s-%s-%s", provider.IstioMcpAutoGeneratedMcpServerName, group, strings.TrimSuffix(dataId, ".json"))
|
||||
httpRoute := vs.Http[0]
|
||||
pathMatchValue := ""
|
||||
for _, match := range httpRoute.Match {
|
||||
if match.Uri != nil && match.Uri.GetExact() != "" {
|
||||
pathMatchValue = match.Uri.GetExact()
|
||||
break
|
||||
}
|
||||
}
|
||||
protocol := server.Protocol
|
||||
|
||||
mcpServer := &mcpserver.McpServer{
|
||||
Name: name,
|
||||
Domains: domains,
|
||||
PathMatchType: mcpserver.PrefixMatchType,
|
||||
PathMatchValue: pathMatchValue,
|
||||
UpstreamType: protocolUpstreamTypeMapping[protocol],
|
||||
}
|
||||
if mcpServerRewriteProtocols[protocol] {
|
||||
mcpServer.EnablePathRewrite = true
|
||||
mcpServer.PathRewritePrefix = "/"
|
||||
}
|
||||
|
||||
mcpServerLog.Debugf("construct mcpserver %v", mcpServer)
|
||||
|
||||
return &config.Config{
|
||||
Meta: config.Meta{
|
||||
GroupVersionKind: mcpserver.GvkMcpServer,
|
||||
Name: name,
|
||||
Namespace: w.namespace,
|
||||
},
|
||||
Spec: mcpServer,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *watcher) generateServiceEntry(host string, services []model.Instance) *v1alpha3.ServiceEntry {
|
||||
portList := make([]*v1alpha3.ServicePort, 0)
|
||||
endpoints := make([]*v1alpha3.WorkloadEntry, 0)
|
||||
@@ -980,3 +1059,13 @@ func isValidIP(ipStr string) bool {
|
||||
ip := net.ParseIP(ipStr)
|
||||
return ip != nil
|
||||
}
|
||||
|
||||
func normalizeRewritePathPrefix(path string) string {
|
||||
if path == "" || path == "/" {
|
||||
return "/"
|
||||
}
|
||||
if path[0] != '/' {
|
||||
path = "/" + path
|
||||
}
|
||||
return strings.TrimSuffix(path, "/")
|
||||
}
|
||||
|
||||
@@ -23,11 +23,12 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/alibaba/higress/registry/nacos/mcpserver"
|
||||
"istio.io/pkg/log"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
apiv1 "github.com/alibaba/higress/api/networking/v1"
|
||||
v1 "github.com/alibaba/higress/client/pkg/apis/networking/v1"
|
||||
higressmcpserver "github.com/alibaba/higress/pkg/ingress/kube/mcpserver"
|
||||
"github.com/alibaba/higress/pkg/kube"
|
||||
. "github.com/alibaba/higress/registry"
|
||||
"github.com/alibaba/higress/registry/consul"
|
||||
@@ -35,9 +36,9 @@ import (
|
||||
"github.com/alibaba/higress/registry/eureka"
|
||||
"github.com/alibaba/higress/registry/memory"
|
||||
"github.com/alibaba/higress/registry/nacos"
|
||||
"github.com/alibaba/higress/registry/nacos/mcpserver"
|
||||
nacosv2 "github.com/alibaba/higress/registry/nacos/v2"
|
||||
"github.com/alibaba/higress/registry/zookeeper"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -320,6 +321,17 @@ func (r *Reconciler) getAuthOption(registry *apiv1.RegistryConfig) (AuthOption,
|
||||
return authOption, nil
|
||||
}
|
||||
|
||||
func (r *Reconciler) GetMcpServers() []*higressmcpserver.McpServer {
|
||||
mcpServersFromMcp := r.GetAllConfigs(higressmcpserver.GvkMcpServer)
|
||||
servers := make([]*higressmcpserver.McpServer, 0, len(mcpServersFromMcp))
|
||||
for _, c := range mcpServersFromMcp {
|
||||
if server, ok := c.Spec.(*higressmcpserver.McpServer); ok {
|
||||
servers = append(servers, server)
|
||||
}
|
||||
}
|
||||
return servers
|
||||
}
|
||||
|
||||
type RegistryWatcherStatus struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
|
||||
Reference in New Issue
Block a user