feat: Refactor mcpServer.matchList config generation logic (#2207)

This commit is contained in:
Kent Dong
2025-05-26 15:26:44 +08:00
committed by GitHub
parent ec83623614
commit ffcf5df28a
17 changed files with 1332 additions and 106 deletions

View File

@@ -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, "/")
}