diff --git a/plugins/golang-filter/README.md b/plugins/golang-filter/README.md index 686c175d3..9b8f52294 100644 --- a/plugins/golang-filter/README.md +++ b/plugins/golang-filter/README.md @@ -54,8 +54,15 @@ http_filters: ## 快速构建 -使用以下命令可以快速构建 golang filter 插件: +使用以下命令可以快速构建 golang filter 插件: ```bash make build ``` + +如果是 arm64 架构,请设置 `GOARCH=arm64`: + +```bash +make build GOARCH=arm64 +``` +你也可以直接在 Higress 项目的根目录下执行 `make build-gateway-local` 来构建 Higress Gateway 镜像,`golang-filter.so` 将会自动构建并复制到镜像中。 diff --git a/plugins/golang-filter/README_en.md b/plugins/golang-filter/README_en.md index fc351c28e..a586a9285 100644 --- a/plugins/golang-filter/README_en.md +++ b/plugins/golang-filter/README_en.md @@ -58,4 +58,12 @@ Use the following command to quickly build the golang filter plugin: ```bash make build -``` \ No newline at end of file +``` + +If you are on an arm64 architecture, please set `GOARCH=arm64`: + +```bash +make build GOARCH=arm64 +``` + +Alternatively, you can build the Higress Gateway image directly by running `make build-gateway-local` in the root directory of the Higress project. The `golang-filter.so` file will be automatically built and included in the image. diff --git a/plugins/golang-filter/mcp-server/config.go b/plugins/golang-filter/mcp-server/config.go index 5be134191..df0997efe 100644 --- a/plugins/golang-filter/mcp-server/config.go +++ b/plugins/golang-filter/mcp-server/config.go @@ -5,6 +5,7 @@ import ( _ "github.com/alibaba/higress/plugins/golang-filter/mcp-server/registry/nacos" _ "github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/gorm" + _ "github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/higress/higress-api" 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" @@ -99,7 +100,7 @@ func (p *Parser) Parse(any *anypb.Any, callbacks api.ConfigCallbackHandler) (int serverInstance, err := server.NewServer(serverName) if err != nil { - return nil, fmt.Errorf("failed to initialize DBServer: %w", err) + return nil, fmt.Errorf("failed to initialize MCP Server: %w", err) } conf.servers = append(conf.servers, &SSEServerWrapper{ diff --git a/plugins/golang-filter/mcp-server/servers/higress/client.go b/plugins/golang-filter/mcp-server/servers/higress/client.go new file mode 100644 index 000000000..e425bed57 --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/client.go @@ -0,0 +1,95 @@ +package higress + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "time" + + "github.com/envoyproxy/envoy/contrib/golang/common/go/api" +) + +// HigressClient handles Higress Console API connections and operations +type HigressClient struct { + baseURL string + username string + password string + httpClient *http.Client +} + +func NewHigressClient(baseURL, username, password string) *HigressClient { + client := &HigressClient{ + baseURL: baseURL, + username: username, + password: password, + httpClient: &http.Client{ + Timeout: 30 * time.Second, + }, + } + + api.LogInfof("Higress Console client initialized: %s", baseURL) + + return client +} + +func (c *HigressClient) Get(path string) ([]byte, error) { + return c.request("GET", path, nil) +} + +func (c *HigressClient) Post(path string, data interface{}) ([]byte, error) { + return c.request("POST", path, data) +} + +func (c *HigressClient) Put(path string, data interface{}) ([]byte, error) { + return c.request("PUT", path, data) +} + +func (c *HigressClient) Delete(path string) ([]byte, error) { + return c.request("DELETE", path, nil) +} +func (c *HigressClient) request(method, path string, data interface{}) ([]byte, error) { + url := c.baseURL + path + + var body io.Reader + if data != nil { + jsonData, err := json.Marshal(data) + if err != nil { + return nil, fmt.Errorf("failed to marshal request data: %w", err) + } + body = bytes.NewBuffer(jsonData) + api.LogDebugf("Higress API %s %s: %s", method, url, string(jsonData)) + } else { + api.LogDebugf("Higress API %s %s", method, url) + } + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + req, err := http.NewRequestWithContext(ctx, method, url, body) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + req.SetBasicAuth(c.username, c.password) + req.Header.Set("Content-Type", "application/json") + + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, fmt.Errorf("request failed: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return nil, fmt.Errorf("HTTP error %d", resp.StatusCode) + } + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %w", err) + } + + return respBody, nil +} diff --git a/plugins/golang-filter/mcp-server/servers/higress/higress-api/README.md b/plugins/golang-filter/mcp-server/servers/higress/higress-api/README.md new file mode 100644 index 000000000..85fefbfc8 --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/higress-api/README.md @@ -0,0 +1,73 @@ +# Higress API MCP Server + +Higress API MCP Server 提供了 MCP 工具来管理 Higress 路由、服务来源和插件等资源。 + +## 功能特性 + +### 路由管理 +- `list-routes`: 列出路由 +- `get-route`: 获取路由 +- `add-route`: 添加路由 +- `update-route`: 更新路由 + +### 服务来源管理 +- `list-service-sources`: 列出服务来源 +- `get-service-source`: 获取服务来源 +- `add-service-source`: 添加服务来源 +- `update-service-source`: 更新服务来源 + +### 插件管理 +- `get-plugin`: 获取插件配置 +- `delete-plugin`: 删除插件 +- `update-request-block-plguin`: 更新 request-block 插件配置 + +## 配置参数 + +| 参数 | 类型 | 必需 | 说明 | +|------|------|------|------| +| `higressURL` | string | 必填 | Higress Console 的 URL 地址 | +| `username` | string | 必填 | Higress Console 登录用户名 | +| `password` | string | 必填 | Higress Console 登录密码 | +| `description` | string | 可选 | 服务器描述信息 | + +配置示例: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + annotations: + meta.helm.sh/release-name: higress + meta.helm.sh/release-namespace: higress-system + labels: + app: higress-gateway + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: higress-gateway + app.kubernetes.io/version: 2.1.4 + helm.sh/chart: higress-core-2.1.4 + higress: higress-system-higress-gateway + name: higress-config + namespace: higress-system +data: + higress: |- + mcpServer: + sse_path_suffix: /sse # SSE 连接的路径后缀 + enable: true # 启用 MCP Server + redis: + address: redis-stack-server.higress-system.svc.cluster.local:6379 # Redis服务地址 + username: "" # Redis用户名(可选) + password: "" # Redis密码(可选) + db: 0 # Redis数据库(可选) + match_list: # MCP Server 会话保持路由规则(当匹配下面路径时,将被识别为一个 MCP 会话,通过 SSE 等机制进行会话保持) + - match_rule_domain: "*" + match_rule_path: /higress-api + match_rule_type: "prefix" + servers: + - name: higress-api-mcp-server # MCP Server 名称 + path: /higress-api # 访问路径,需要与 match_list 中的配置匹配 + type: higress-api # 类型和 RegisterServer 一致 + config: + higressURL: http://higress-console.higress-system.svc.cluster.local:8080 + username: admin + password: admin +``` diff --git a/plugins/golang-filter/mcp-server/servers/higress/higress-api/README_en.md b/plugins/golang-filter/mcp-server/servers/higress/higress-api/README_en.md new file mode 100644 index 000000000..f2ee92092 --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/higress-api/README_en.md @@ -0,0 +1,73 @@ +# Higress API MCP Server + +Higress API MCP Server provides MCP tools to manage Higress routes, service sources, plugins and other resources. + +## Features + +### Route Management +- `list-routes`: List routes +- `get-route`: Get route +- `add-route`: Add route +- `update-route`: Update route + +### Service Source Management +- `list-service-sources`: List service sources +- `get-service-source`: Get service source +- `add-service-source`: Add service source +- `update-service-source`: Update service source + +### Plugin Management +- `get-plugin`: Get plugin configuration +- `delete-plugin`: Delete plugin +- `update-request-block-plugin`: Update request block configuration + +## Configuration Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `higressURL` | string | Required | Higress Console URL address | +| `username` | string | Required | Higress Console login username | +| `password` | string | Required | Higress Console login password | +| `description` | string | Optional | MCP Server description | + +Configuration Example: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + annotations: + meta.helm.sh/release-name: higress + meta.helm.sh/release-namespace: higress-system + labels: + app: higress-gateway + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: higress-gateway + app.kubernetes.io/version: 2.1.4 + helm.sh/chart: higress-core-2.1.4 + higress: higress-system-higress-gateway + name: higress-config + namespace: higress-system +data: + higress: |- + mcpServer: + sse_path_suffix: /sse # SSE connection path suffix + enable: true # Enable MCP Server + redis: + address: redis-stack-server.higress-system.svc.cluster.local:6379 # Redis service address + username: "" # Redis username (optional) + password: "" # Redis password (optional) + db: 0 # Redis database (optional) + match_list: # MCP Server session persistence routing rules (when matching the following paths, it will be recognized as an MCP session and maintained through SSE) + - match_rule_domain: "*" + match_rule_path: /higress-api + match_rule_type: "prefix" + servers: + - name: higress-api-mcp-server # MCP Server name + path: /higress-api # Access path, needs to match the configuration in match_list + type: higress-api # Type defined in RegisterServer function + config: + higressURL: http://higress-console.higress-system.svc.cluster.local:8080 + username: admin + password: admin +``` diff --git a/plugins/golang-filter/mcp-server/servers/higress/higress-api/server.go b/plugins/golang-filter/mcp-server/servers/higress/higress-api/server.go new file mode 100644 index 000000000..88930f506 --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/higress-api/server.go @@ -0,0 +1,76 @@ +package higress_ops + +import ( + "errors" + + "github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/higress" + "github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/higress/higress-api/tools" + "github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/higress/higress-api/tools/plugins" + "github.com/alibaba/higress/plugins/golang-filter/mcp-session/common" + "github.com/envoyproxy/envoy/contrib/golang/common/go/api" +) + +const Version = "1.0.0" + +func init() { + common.GlobalRegistry.RegisterServer("higress-api", &HigressConfig{}) +} + +type HigressConfig struct { + higressURL string + username string + password string + description string +} + +func (c *HigressConfig) ParseConfig(config map[string]interface{}) error { + higressURL, ok := config["higressURL"].(string) + if !ok { + return errors.New("missing higressURL") + } + c.higressURL = higressURL + + username, ok := config["username"].(string) + if !ok { + return errors.New("missing username") + } + c.username = username + + password, ok := config["password"].(string) + if !ok { + return errors.New("missing password") + } + c.password = password + + if desc, ok := config["description"].(string); ok { + c.description = desc + } else { + c.description = "Higress API MCP Server, which invokes Higress Console APIs to manage resources such as routes, services, and plugins." + } + + api.LogDebugf("HigressConfig ParseConfig: higressURL=%s, username=%s, description=%s", + c.higressURL, c.username, c.description) + + return nil +} + +func (c *HigressConfig) NewServer(serverName string) (*common.MCPServer, error) { + mcpServer := common.NewMCPServer( + serverName, + Version, + common.WithInstructions("This is a Higress API MCP Server"), + ) + + // Initialize Higress API client + client := higress.NewHigressClient(c.higressURL, c.username, c.password) + + // Register all tools + tools.RegisterRouteTools(mcpServer, client) + tools.RegisterServiceTools(mcpServer, client) + plugins.RegisterCommonPluginTools(mcpServer, client) + plugins.RegisterRequestBlockPluginTools(mcpServer, client) + + api.LogInfof("Higress MCP Server initialized: %s", serverName) + + return mcpServer, nil +} diff --git a/plugins/golang-filter/mcp-server/servers/higress/higress-api/tools/plugins/common.go b/plugins/golang-filter/mcp-server/servers/higress/higress-api/tools/plugins/common.go new file mode 100644 index 000000000..b50870edf --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/higress-api/tools/plugins/common.go @@ -0,0 +1,141 @@ +package plugins + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/higress" + "github.com/alibaba/higress/plugins/golang-filter/mcp-session/common" + "github.com/mark3labs/mcp-go/mcp" +) + +// RegisterCommonPluginTools registers all common plugin management tools +func RegisterCommonPluginTools(mcpServer *common.MCPServer, client *higress.HigressClient) { + // Get plugin configuration + mcpServer.AddTool( + mcp.NewToolWithRawSchema("get-plugin", "Get configuration for a specific plugin", getPluginConfigSchema()), + handleGetPluginConfig(client), + ) + + // Delete plugin configuration + mcpServer.AddTool( + mcp.NewToolWithRawSchema("delete-plugin", "Delete configuration for a specific plugin", getPluginConfigSchema()), + handleDeletePluginConfig(client), + ) +} + +func handleGetPluginConfig(client *higress.HigressClient) common.ToolHandlerFunc { + return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + arguments := request.Params.Arguments + + // Parse required parameters + pluginName, ok := arguments["name"].(string) + if !ok { + return nil, fmt.Errorf("missing or invalid 'name' argument") + } + + scope, ok := arguments["scope"].(string) + if !ok { + return nil, fmt.Errorf("missing or invalid 'scope' argument") + } + + if !IsValidScope(scope) { + return nil, fmt.Errorf("invalid scope '%s', must be one of: %v", scope, ValidScopes) + } + + // Parse resource_name (required for non-global scopes) + var resourceName string + if scope != ScopeGlobal { + resourceName, ok = arguments["resource_name"].(string) + if !ok || resourceName == "" { + return nil, fmt.Errorf("'resource_name' is required for scope '%s'", scope) + } + } + + // Build API path and make request + path := BuildPluginPath(pluginName, scope, resourceName) + respBody, err := client.Get(path) + if err != nil { + return nil, fmt.Errorf("failed to get plugin config for '%s' at scope '%s': %w", pluginName, scope, err) + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.TextContent{ + Type: "text", + Text: string(respBody), + }, + }, + }, nil + } +} + +func handleDeletePluginConfig(client *higress.HigressClient) common.ToolHandlerFunc { + return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + arguments := request.Params.Arguments + + // Parse required parameters + pluginName, ok := arguments["name"].(string) + if !ok { + return nil, fmt.Errorf("missing or invalid 'name' argument") + } + + scope, ok := arguments["scope"].(string) + if !ok { + return nil, fmt.Errorf("missing or invalid 'scope' argument") + } + + if !IsValidScope(scope) { + return nil, fmt.Errorf("invalid scope '%s', must be one of: %v", scope, ValidScopes) + } + + // Parse resource_name (required for non-global scopes) + var resourceName string + if scope != ScopeGlobal { + resourceName, ok = arguments["resource_name"].(string) + if !ok || resourceName == "" { + return nil, fmt.Errorf("'resource_name' is required for scope '%s'", scope) + } + } + + // Build API path and make request + path := BuildPluginPath(pluginName, scope, resourceName) + respBody, err := client.Delete(path) + if err != nil { + return nil, fmt.Errorf("failed to delete plugin config for '%s' at scope '%s': %w", pluginName, scope, err) + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.TextContent{ + Type: "text", + Text: string(respBody), + }, + }, + }, nil + } +} + +func getPluginConfigSchema() json.RawMessage { + return json.RawMessage(`{ + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the plugin" + }, + "scope": { + "type": "string", + "enum": ["GLOBAL", "DOMAIN", "SERVICE", "ROUTE"], + "description": "The scope at which the plugin is applied" + }, + "resource_name": { + "type": "string", + "description": "The name of the resource (required for DOMAIN, SERVICE, ROUTE scopes)" + } + }, + "required": ["name", "scope"], + "additionalProperties": false + }`) +} diff --git a/plugins/golang-filter/mcp-server/servers/higress/higress-api/tools/plugins/request-block.go b/plugins/golang-filter/mcp-server/servers/higress/higress-api/tools/plugins/request-block.go new file mode 100644 index 000000000..5d9450a5e --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/higress-api/tools/plugins/request-block.go @@ -0,0 +1,186 @@ +package plugins + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/higress" + "github.com/alibaba/higress/plugins/golang-filter/mcp-session/common" + "github.com/mark3labs/mcp-go/mcp" +) + +const RequestBlockPluginName = "request-block" + +// RequestBlockConfig represents the configuration for request-block plugin +type RequestBlockConfig struct { + BlockBodies []string `json:"block_bodies,omitempty"` + BlockHeaders []string `json:"block_headers,omitempty"` + BlockUrls []string `json:"block_urls,omitempty"` + BlockedCode int `json:"blocked_code,omitempty"` + CaseSensitive bool `json:"case_sensitive,omitempty"` +} + +// RequestBlockInstance represents a request-block plugin instance +type RequestBlockInstance = PluginInstance[RequestBlockConfig] + +// RequestBlockResponse represents the API response for request-block plugin +type RequestBlockResponse = higress.APIResponse[RequestBlockInstance] + +// RegisterRequestBlockPluginTools registers all request block plugin management tools +func RegisterRequestBlockPluginTools(mcpServer *common.MCPServer, client *higress.HigressClient) { + // Update request block configuration + mcpServer.AddTool( + mcp.NewToolWithRawSchema(fmt.Sprintf("update-%s-plugin", RequestBlockPluginName), "Update request block plugin configuration", getAddOrUpdateRequestBlockConfigSchema()), + handleAddOrUpdateRequestBlockConfig(client), + ) +} + +func handleAddOrUpdateRequestBlockConfig(client *higress.HigressClient) common.ToolHandlerFunc { + return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + arguments := request.Params.Arguments + + // Parse required parameters + scope, ok := arguments["scope"].(string) + if !ok { + return nil, fmt.Errorf("missing or invalid 'scope' argument") + } + + if !IsValidScope(scope) { + return nil, fmt.Errorf("invalid scope '%s', must be one of: %v", scope, ValidScopes) + } + + enabled, ok := arguments["enabled"].(bool) + if !ok { + return nil, fmt.Errorf("missing or invalid 'enabled' argument") + } + + configurations, ok := arguments["configurations"] + if !ok { + return nil, fmt.Errorf("missing 'configurations' argument") + } + + // Parse resource_name for non-global scopes + var resourceName string + if scope != ScopeGlobal { + // Validate and get resource_name + resourceName, ok = arguments["resource_name"].(string) + if !ok || resourceName == "" { + return nil, fmt.Errorf("'resource_name' is required for scope '%s'", scope) + } + } + + // Build API path + path := BuildPluginPath(RequestBlockPluginName, scope, resourceName) + + // Get current request block configuration to merge with updates + currentBody, err := client.Get(path) + if err != nil { + return nil, fmt.Errorf("failed to get current request block configuration: %w", err) + } + + var response RequestBlockResponse + if err := json.Unmarshal(currentBody, &response); err != nil { + return nil, fmt.Errorf("failed to parse current request block response: %w", err) + } + + currentConfig := response.Data + currentConfig.Enabled = enabled + currentConfig.Scope = scope + + // Convert the input configurations to RequestBlockConfig and merge + configBytes, err := json.Marshal(configurations) + if err != nil { + return nil, fmt.Errorf("failed to marshal configurations: %w", err) + } + + var newConfig RequestBlockConfig + if err := json.Unmarshal(configBytes, &newConfig); err != nil { + return nil, fmt.Errorf("failed to parse request block configurations: %w", err) + } + + // Update configurations (overwrite with new values where provided) + if newConfig.BlockBodies != nil { + currentConfig.Configurations.BlockBodies = newConfig.BlockBodies + } + if newConfig.BlockHeaders != nil { + currentConfig.Configurations.BlockHeaders = newConfig.BlockHeaders + } + if newConfig.BlockUrls != nil { + currentConfig.Configurations.BlockUrls = newConfig.BlockUrls + } + if newConfig.BlockedCode != 0 { + currentConfig.Configurations.BlockedCode = newConfig.BlockedCode + } + currentConfig.Configurations.CaseSensitive = newConfig.CaseSensitive + + respBody, err := client.Put(path, currentConfig) + if err != nil { + return nil, fmt.Errorf("failed to update request block config at scope '%s': %w", scope, err) + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.TextContent{ + Type: "text", + Text: string(respBody), + }, + }, + }, nil + } +} + +func getAddOrUpdateRequestBlockConfigSchema() json.RawMessage { + return json.RawMessage(`{ + "type": "object", + "properties": { + "scope": { + "type": "string", + "enum": ["GLOBAL", "DOMAIN", "SERVICE", "ROUTE"], + "description": "The scope at which the plugin is applied" + }, + + "resource_name": { + "type": "string", + "description": "The name of the resource (required for DOMAIN, SERVICE, ROUTE scopes)" + }, + "enabled": { + "type": "boolean", + "description": "Whether the plugin is enabled" + }, + "configurations": { + "type": "object", + "properties": { + "block_bodies": { + "type": "array", + "items": {"type": "string"}, + "description": "List of patterns to match against request body content" + }, + "block_headers": { + "type": "array", + "items": {"type": "string"}, + "description": "List of patterns to match against request headers" + }, + "block_urls": { + "type": "array", + "items": {"type": "string"}, + "description": "List of patterns to match against request URLs" + }, + "blocked_code": { + "type": "integer", + "minimum": 100, + "maximum": 599, + "description": "HTTP status code to return when a block is matched" + }, + "case_sensitive": { + "type": "boolean", + "description": "Whether the block matching is case sensitive" + } + }, + "additionalProperties": false + } + }, + "required": ["scope", "enabled", "configurations"], + "additionalProperties": false + }`) +} diff --git a/plugins/golang-filter/mcp-server/servers/higress/higress-api/tools/plugins/types.go b/plugins/golang-filter/mcp-server/servers/higress/higress-api/tools/plugins/types.go new file mode 100644 index 000000000..784ebb98c --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/higress-api/tools/plugins/types.go @@ -0,0 +1,22 @@ +package plugins + +// PluginTargets represents the targets for different scopes +type PluginTargets struct { + Domain string `json:"DOMAIN,omitempty"` + Service string `json:"SERVICE,omitempty"` + Route string `json:"ROUTE,omitempty"` +} + +// PluginInstance represents a plugin instance configuration +type PluginInstance[T any] struct { + Version string `json:"version,omitempty"` + Scope string `json:"scope"` + Target string `json:"target,omitempty"` + Targets PluginTargets `json:"targets,omitempty"` + PluginName string `json:"pluginName,omitempty"` + PluginVersion string `json:"pluginVersion,omitempty"` + Internal bool `json:"internal,omitempty"` + Enabled bool `json:"enabled"` + RawConfigurations string `json:"rawConfigurations,omitempty"` + Configurations T `json:"configurations,omitempty"` +} diff --git a/plugins/golang-filter/mcp-server/servers/higress/higress-api/tools/plugins/util.go b/plugins/golang-filter/mcp-server/servers/higress/higress-api/tools/plugins/util.go new file mode 100644 index 000000000..b09e02e3c --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/higress-api/tools/plugins/util.go @@ -0,0 +1,39 @@ +package plugins + +import "fmt" + +const ( + ScopeGlobal = "GLOBAL" + ScopeDomain = "DOMAIN" + ScopeService = "SERVICE" + ScopeRoute = "ROUTE" +) + +// ValidScopes contains all valid plugin scopes +var ValidScopes = []string{ScopeGlobal, ScopeDomain, ScopeService, ScopeRoute} + +// IsValidScope checks if the given scope is valid +func IsValidScope(scope string) bool { + for _, validScope := range ValidScopes { + if scope == validScope { + return true + } + } + return false +} + +// BuildPluginPath builds the API path for plugin operations based on scope and resource +func BuildPluginPath(pluginName, scope, resourceName string) string { + switch scope { + case ScopeGlobal: + return fmt.Sprintf("/v1/global/plugin-instances/%s", pluginName) + case ScopeDomain: + return fmt.Sprintf("/v1/domains/%s/plugin-instances/%s", resourceName, pluginName) + case ScopeService: + return fmt.Sprintf("/v1/services/%s/plugin-instances/%s", resourceName, pluginName) + case ScopeRoute: + return fmt.Sprintf("/v1/routes/%s/plugin-instances/%s", resourceName, pluginName) + default: + return fmt.Sprintf("/v1/global/plugin-instances/%s", pluginName) + } +} diff --git a/plugins/golang-filter/mcp-server/servers/higress/higress-api/tools/route.go b/plugins/golang-filter/mcp-server/servers/higress/higress-api/tools/route.go new file mode 100644 index 000000000..7e5d15094 --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/higress-api/tools/route.go @@ -0,0 +1,456 @@ +package tools + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/higress" + "github.com/alibaba/higress/plugins/golang-filter/mcp-session/common" + "github.com/mark3labs/mcp-go/mcp" +) + +// Route represents a route configuration +type Route struct { + Name string `json:"name"` + Version string `json:"version,omitempty"` + Domains []string `json:"domains,omitempty"` + Path *RoutePath `json:"path,omitempty"` + Methods []string `json:"methods,omitempty"` + Headers []RouteMatch `json:"headers,omitempty"` + URLParams []RouteMatch `json:"urlParams,omitempty"` + Services []RouteService `json:"services,omitempty"` + AuthConfig *RouteAuthConfig `json:"authConfig,omitempty"` + CustomConfigs map[string]interface{} `json:"customConfigs,omitempty"` +} + +// RoutePath represents path matching configuration +type RoutePath struct { + MatchType string `json:"matchType"` + MatchValue string `json:"matchValue"` + CaseSensitive bool `json:"caseSensitive,omitempty"` +} + +// RouteMatch represents header or URL parameter matching configuration +type RouteMatch struct { + Key string `json:"key"` + MatchType string `json:"matchType"` + MatchValue string `json:"matchValue"` +} + +// RouteService represents a service in the route +type RouteService struct { + Name string `json:"name"` + Port int `json:"port"` + Weight int `json:"weight"` +} + +// RouteAuthConfig represents authentication configuration for a route +type RouteAuthConfig struct { + Enabled bool `json:"enabled"` + AllowedConsumers []string `json:"allowedConsumers,omitempty"` +} + +// RouteResponse represents the API response for route operations +type RouteResponse = higress.APIResponse[Route] + +// RegisterRouteTools registers all route management tools +func RegisterRouteTools(mcpServer *common.MCPServer, client *higress.HigressClient) { + // List all routes + mcpServer.AddTool( + mcp.NewTool("list-routes", mcp.WithDescription("List all available routes")), + handleListRoutes(client), + ) + + // Get specific route + mcpServer.AddTool( + mcp.NewToolWithRawSchema("get-route", "Get detailed information about a specific route", getRouteSchema()), + handleGetRoute(client), + ) + + // Add new route + mcpServer.AddTool( + mcp.NewToolWithRawSchema("add-route", "Add a new route", getAddRouteSchema()), + handleAddRoute(client), + ) + + // Update existing route + mcpServer.AddTool( + mcp.NewToolWithRawSchema("update-route", "Update an existing route", getUpdateRouteSchema()), + handleUpdateRoute(client), + ) + + // Delete existing route + mcpServer.AddTool( + mcp.NewToolWithRawSchema("delete-route", "Delete an existing route", getRouteSchema()), + handleDeleteRoute(client), + ) +} + +func handleListRoutes(client *higress.HigressClient) common.ToolHandlerFunc { + return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + respBody, err := client.Get("/v1/routes") + if err != nil { + return nil, fmt.Errorf("failed to list routes: %w", err) + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.TextContent{ + Type: "text", + Text: string(respBody), + }, + }, + }, nil + } +} + +func handleGetRoute(client *higress.HigressClient) common.ToolHandlerFunc { + return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + arguments := request.Params.Arguments + name, ok := arguments["name"].(string) + if !ok { + return nil, fmt.Errorf("missing or invalid 'name' argument") + } + + respBody, err := client.Get(fmt.Sprintf("/v1/routes/%s", name)) + if err != nil { + return nil, fmt.Errorf("failed to get route '%s': %w", name, err) + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.TextContent{ + Type: "text", + Text: string(respBody), + }, + }, + }, nil + } +} + +func handleAddRoute(client *higress.HigressClient) common.ToolHandlerFunc { + return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + arguments := request.Params.Arguments + configurations, ok := arguments["configurations"].(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("missing or invalid 'configurations' argument") + } + + // Validate required fields + if _, ok := configurations["name"]; !ok { + return nil, fmt.Errorf("missing required field 'name' in configurations") + } + if _, ok := configurations["path"]; !ok { + return nil, fmt.Errorf("missing required field 'path' in configurations") + } + if _, ok := configurations["services"]; !ok { + return nil, fmt.Errorf("missing required field 'services' in configurations") + } + + respBody, err := client.Post("/v1/routes", configurations) + if err != nil { + return nil, fmt.Errorf("failed to add route: %w", err) + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.TextContent{ + Type: "text", + Text: string(respBody), + }, + }, + }, nil + } +} + +func handleUpdateRoute(client *higress.HigressClient) common.ToolHandlerFunc { + return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + arguments := request.Params.Arguments + name, ok := arguments["name"].(string) + if !ok { + return nil, fmt.Errorf("missing or invalid 'name' argument") + } + + configurations, ok := arguments["configurations"].(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("missing or invalid 'configurations' argument") + } + + // Get current route configuration to merge with updates + currentBody, err := client.Get(fmt.Sprintf("/v1/routes/%s", name)) + if err != nil { + return nil, fmt.Errorf("failed to get current route configuration: %w", err) + } + + var response RouteResponse + if err := json.Unmarshal(currentBody, &response); err != nil { + return nil, fmt.Errorf("failed to parse current route response: %w", err) + } + + currentConfig := response.Data + + // Update configurations using JSON marshal/unmarshal for type conversion + configBytes, err := json.Marshal(configurations) + if err != nil { + return nil, fmt.Errorf("failed to marshal configurations: %w", err) + } + + var newConfig Route + if err := json.Unmarshal(configBytes, &newConfig); err != nil { + return nil, fmt.Errorf("failed to parse route configurations: %w", err) + } + + // Merge configurations (overwrite with new values where provided) + if newConfig.Domains != nil { + currentConfig.Domains = newConfig.Domains + } + if newConfig.Path != nil { + currentConfig.Path = newConfig.Path + } + if newConfig.Methods != nil { + currentConfig.Methods = newConfig.Methods + } + if newConfig.Headers != nil { + currentConfig.Headers = newConfig.Headers + } + if newConfig.URLParams != nil { + currentConfig.URLParams = newConfig.URLParams + } + if newConfig.Services != nil { + currentConfig.Services = newConfig.Services + } + if newConfig.AuthConfig != nil { + currentConfig.AuthConfig = newConfig.AuthConfig + } + if newConfig.CustomConfigs != nil { + currentConfig.CustomConfigs = newConfig.CustomConfigs + } + + respBody, err := client.Put(fmt.Sprintf("/v1/routes/%s", name), currentConfig) + if err != nil { + return nil, fmt.Errorf("failed to update route '%s': %w", name, err) + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.TextContent{ + Type: "text", + Text: string(respBody), + }, + }, + }, nil + } +} + +func handleDeleteRoute(client *higress.HigressClient) common.ToolHandlerFunc { + return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + arguments := request.Params.Arguments + name, ok := arguments["name"].(string) + if !ok { + return nil, fmt.Errorf("missing or invalid 'name' argument") + } + + respBody, err := client.Delete(fmt.Sprintf("/v1/routes/%s", name)) + if err != nil { + return nil, fmt.Errorf("failed to delete route '%s': %w", name, err) + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.TextContent{ + Type: "text", + Text: string(respBody), + }, + }, + }, nil + } +} + +func getRouteSchema() json.RawMessage { + return json.RawMessage(`{ + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the route" + } + }, + "required": ["name"], + "additionalProperties": false + }`) +} + +func getAddRouteSchema() json.RawMessage { + return json.RawMessage(`{ + "type": "object", + "properties": { + "configurations": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the route" + }, + "domains": { + "type": "array", + "items": {"type": "string"}, + "description": "List of domain names, but only one domain is allowed" + }, + "path": { + "type": "object", + "properties": { + "matchType": {"type": "string", "enum": ["PRE", "EQUAL", "REGULAR"], "description": "Match type of path"}, + "matchValue": {"type": "string", "description": "Value to match"}, + "caseSensitive": {"type": "boolean", "description": "Whether matching is case sensitive"} + }, + "required": ["matchType", "matchValue"], + "description": "List of path match conditions" + }, + "methods": { + "type": "array", + "items": {"type": "string", "enum": ["GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH", "TRACE", "CONNECT"]}, + "description": "List of HTTP methods" + }, + "headers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "matchType": {"type": "string", "enum": ["PRE", "EQUAL", "REGULAR"], "description": "Match type of header"}, + "matchValue": {"type": "string", "description": "Value to match"}, + "caseSensitive": {"type": "boolean", "description": "Whether matching is case sensitive"}, + "key": {"type": "string", "description": "Header key name"} + }, + "required": ["matchType", "matchValue", "key"] + }, + "description": "List of header match conditions" + }, + "urlParams": { + "type": "array", + "items": { + "type": "object", + "properties": { + "matchType": {"type": "string", "enum": ["PRE", "EQUAL", "REGULAR"], "description": "Match type of URL parameter"}, + "matchValue": {"type": "string", "description": "Value to match"}, + "caseSensitive": {"type": "boolean", "description": "Whether matching is case sensitive"}, + "key": {"type": "string", "description": "Parameter key name"} + }, + "required": ["matchType", "matchValue", "key"] + }, + "description": "List of URL parameter match conditions" + }, + "services": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": {"type": "string", "description": "Service name"}, + "port": {"type": "integer", "description": "Service port"}, + "weight": {"type": "integer", "description": "Service weight"} + }, + "required": ["name", "port", "weight"] + }, + "description": "List of services for this route" + }, + "customConfigs": { + "type": "object", + "additionalProperties": {"type": "string"}, + "description": "Dictionary of custom configurations" + } + }, + "required": ["name", "path", "services"], + "additionalProperties": false + } + }, + "required": ["configurations"], + "additionalProperties": false + }`) +} + +func getUpdateRouteSchema() json.RawMessage { + return json.RawMessage(`{ + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the route" + }, + "configurations": { + "type": "object", + "properties": { + "domains": { + "type": "array", + "items": {"type": "string"}, + "description": "List of domain names, but only one domain is allowed", + "maxItems": 1 + }, + "path": { + "type": "object", + "properties": { + "matchType": {"type": "string", "enum": ["PRE", "EQUAL", "REGULAR"], "description": "Match type of path"}, + "matchValue": {"type": "string", "description": "Value to match"}, + "caseSensitive": {"type": "boolean", "description": "Whether matching is case sensitive"} + }, + "required": ["matchType", "matchValue"], + "description": "The path configuration" + }, + "methods": { + "type": "array", + "items": {"type": "string", "enum": ["GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH", "TRACE", "CONNECT"]}, + "description": "List of HTTP methods" + }, + "headers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "matchType": {"type": "string", "enum": ["PRE", "EQUAL", "REGULAR"], "description": "Match type of header"}, + "matchValue": {"type": "string", "description": "Value to match"}, + "caseSensitive": {"type": "boolean", "description": "Whether matching is case sensitive"}, + "key": {"type": "string", "description": "Header key name"} + }, + "required": ["matchType", "matchValue", "key"] + }, + "description": "List of header match conditions" + }, + "urlParams": { + "type": "array", + "items": { + "type": "object", + "properties": { + "matchType": {"type": "string", "enum": ["PRE", "EQUAL", "REGULAR"], "description": "Match type of URL parameter"}, + "matchValue": {"type": "string", "description": "Value to match"}, + "caseSensitive": {"type": "boolean", "description": "Whether matching is case sensitive"}, + "key": {"type": "string", "description": "Parameter key name"} + }, + "required": ["matchType", "matchValue", "key"] + }, + "description": "List of URL parameter match conditions" + }, + "services": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": {"type": "string", "description": "Service name"}, + "port": {"type": "integer", "description": "Service port"}, + "weight": {"type": "integer", "description": "Service weight"} + }, + "required": ["name", "port", "weight"] + }, + "description": "List of services for this route" + }, + "customConfigs": { + "type": "object", + "additionalProperties": {"type": "string"}, + "description": "Dictionary of custom configurations" + } + }, + "additionalProperties": false + } + }, + "required": ["name", "configurations"], + "additionalProperties": false + }`) +} diff --git a/plugins/golang-filter/mcp-server/servers/higress/higress-api/tools/service.go b/plugins/golang-filter/mcp-server/servers/higress/higress-api/tools/service.go new file mode 100644 index 000000000..1f9e6b1d8 --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/higress-api/tools/service.go @@ -0,0 +1,355 @@ +package tools + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/higress" + "github.com/alibaba/higress/plugins/golang-filter/mcp-session/common" + "github.com/mark3labs/mcp-go/mcp" +) + +// ServiceSource represents a service source configuration +type ServiceSource struct { + Name string `json:"name"` + Version string `json:"version,omitempty"` + Type string `json:"type"` + Domain string `json:"domain"` + Port int `json:"port"` + Protocol string `json:"protocol,omitempty"` + SNI *string `json:"sni,omitempty"` + Properties map[string]interface{} `json:"properties,omitempty"` + AuthN *ServiceSourceAuthN `json:"authN,omitempty"` + Valid bool `json:"valid,omitempty"` +} + +// ServiceSourceAuthN represents authentication configuration for service source +type ServiceSourceAuthN struct { + Enabled bool `json:"enabled"` + Properties map[string]interface{} `json:"properties,omitempty"` +} + +// ServiceSourceResponse represents the API response for service source operations +type ServiceSourceResponse = higress.APIResponse[ServiceSource] + +// RegisterServiceTools registers all service source management tools +func RegisterServiceTools(mcpServer *common.MCPServer, client *higress.HigressClient) { + // List all service sources + mcpServer.AddTool( + mcp.NewTool("list-service-sources", mcp.WithDescription("List all available service sources")), + handleListServiceSources(client), + ) + + // Get specific service source + mcpServer.AddTool( + mcp.NewToolWithRawSchema("get-service-source", "Get detailed information about a specific service source", getServiceSourceSchema()), + handleGetServiceSource(client), + ) + + // Add new service source + mcpServer.AddTool( + mcp.NewToolWithRawSchema("add-service-source", "Add a new service source", getAddServiceSourceSchema()), + handleAddServiceSource(client), + ) + + // Update existing service source + mcpServer.AddTool( + mcp.NewToolWithRawSchema("update-service-source", "Update an existing service source", getUpdateServiceSourceSchema()), + handleUpdateServiceSource(client), + ) + + // Delete existing service source + mcpServer.AddTool( + mcp.NewToolWithRawSchema("delete-service-source", "Delete an existing service source", getServiceSourceSchema()), + handleDeleteServiceSource(client), + ) +} + +func handleListServiceSources(client *higress.HigressClient) common.ToolHandlerFunc { + return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + respBody, err := client.Get("/v1/service-sources") + if err != nil { + return nil, fmt.Errorf("failed to list service sources: %w", err) + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.TextContent{ + Type: "text", + Text: string(respBody), + }, + }, + }, nil + } +} + +func handleGetServiceSource(client *higress.HigressClient) common.ToolHandlerFunc { + return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + arguments := request.Params.Arguments + name, ok := arguments["name"].(string) + if !ok { + return nil, fmt.Errorf("missing or invalid 'name' argument") + } + + respBody, err := client.Get(fmt.Sprintf("/v1/service-sources/%s", name)) + if err != nil { + return nil, fmt.Errorf("failed to get service source '%s': %w", name, err) + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.TextContent{ + Type: "text", + Text: string(respBody), + }, + }, + }, nil + } +} + +func handleAddServiceSource(client *higress.HigressClient) common.ToolHandlerFunc { + return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + arguments := request.Params.Arguments + configurations, ok := arguments["configurations"].(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("missing or invalid 'configurations' argument") + } + + // Validate required fields + if _, ok := configurations["name"]; !ok { + return nil, fmt.Errorf("missing required field 'name' in configurations") + } + if _, ok := configurations["type"]; !ok { + return nil, fmt.Errorf("missing required field 'type' in configurations") + } + if _, ok := configurations["domain"]; !ok { + return nil, fmt.Errorf("missing required field 'domain' in configurations") + } + if _, ok := configurations["port"]; !ok { + return nil, fmt.Errorf("missing required field 'port' in configurations") + } + + respBody, err := client.Post("/v1/service-sources", configurations) + if err != nil { + return nil, fmt.Errorf("failed to add service source: %w", err) + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.TextContent{ + Type: "text", + Text: string(respBody), + }, + }, + }, nil + } +} + +func handleUpdateServiceSource(client *higress.HigressClient) common.ToolHandlerFunc { + return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + arguments := request.Params.Arguments + name, ok := arguments["name"].(string) + if !ok { + return nil, fmt.Errorf("missing or invalid 'name' argument") + } + + configurations, ok := arguments["configurations"].(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("missing or invalid 'configurations' argument") + } + + // Get current service source configuration to merge with updates + currentBody, err := client.Get(fmt.Sprintf("/v1/service-sources/%s", name)) + if err != nil { + return nil, fmt.Errorf("failed to get current service source configuration: %w", err) + } + + var response ServiceSourceResponse + if err := json.Unmarshal(currentBody, &response); err != nil { + return nil, fmt.Errorf("failed to parse current service source response: %w", err) + } + + currentConfig := response.Data + + // Update configurations using JSON marshal/unmarshal for type conversion + configBytes, err := json.Marshal(configurations) + if err != nil { + return nil, fmt.Errorf("failed to marshal configurations: %w", err) + } + + var newConfig ServiceSource + if err := json.Unmarshal(configBytes, &newConfig); err != nil { + return nil, fmt.Errorf("failed to parse service source configurations: %w", err) + } + + // Merge configurations (overwrite with new values where provided) + if newConfig.Name != "" { + currentConfig.Name = newConfig.Name + } + if newConfig.Type != "" { + currentConfig.Type = newConfig.Type + } + if newConfig.Domain != "" { + currentConfig.Domain = newConfig.Domain + } + if newConfig.Port != 0 { + currentConfig.Port = newConfig.Port + } + if newConfig.Protocol != "" { + currentConfig.Protocol = newConfig.Protocol + } + if newConfig.SNI != nil { + currentConfig.SNI = newConfig.SNI + } + if newConfig.Properties != nil { + currentConfig.Properties = newConfig.Properties + } + if newConfig.AuthN != nil { + currentConfig.AuthN = newConfig.AuthN + } + + respBody, err := client.Put(fmt.Sprintf("/v1/service-sources/%s", name), currentConfig) + if err != nil { + return nil, fmt.Errorf("failed to update service source '%s': %w", name, err) + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.TextContent{ + Type: "text", + Text: string(respBody), + }, + }, + }, nil + } +} + +func handleDeleteServiceSource(client *higress.HigressClient) common.ToolHandlerFunc { + return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + arguments := request.Params.Arguments + name, ok := arguments["name"].(string) + if !ok { + return nil, fmt.Errorf("missing or invalid 'name' argument") + } + + respBody, err := client.Delete(fmt.Sprintf("/v1/service-sources/%s", name)) + if err != nil { + return nil, fmt.Errorf("failed to delete service source '%s': %w", name, err) + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.TextContent{ + Type: "text", + Text: string(respBody), + }, + }, + }, nil + } +} + +func getServiceSourceSchema() json.RawMessage { + return json.RawMessage(`{ + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the service source to retrieve" + } + }, + "required": ["name"], + "additionalProperties": false + }`) +} + +// TODO: extend other types of service sources, e.g., nacos, zookeeper, euraka. +func getAddServiceSourceSchema() json.RawMessage { + return json.RawMessage(`{ + "type": "object", + "properties": { + "configurations": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the service source" + }, + "type": { + "type": "string", + "enum": ["static", "dns"], + "description": "The type of service source: 'static' for static IPs, 'dns' for DNS resolution" + }, + "domain": { + "type": "string", + "description": "The domain name or IP address (required)" + }, + "port": { + "type": "integer", + "minimum": 1, + "maximum": 65535, + "description": "The port number (required)" + }, + "protocol": { + "type": "string", + "enum": ["http", "https"], + "description": "The protocol to use (optional, defaults to http)" + }, + "sni": { + "type": "string", + "description": "Server Name Indication for HTTPS connections (optional)" + } + }, + "required": ["name", "type", "domain", "port"], + "additionalProperties": false + } + }, + "required": ["configurations"], + "additionalProperties": false + }`) +} + +// TODO: extend other types of service sources, e.g., nacos, zookeeper, euraka. +func getUpdateServiceSourceSchema() json.RawMessage { + return json.RawMessage(`{ + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the service source to update" + }, + "configurations": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["static", "dns"], + "description": "The type of service source: 'static' for static IPs, 'dns' for DNS resolution" + }, + "domain": { + "type": "string", + "description": "The domain name or IP address" + }, + "port": { + "type": "integer", + "minimum": 1, + "maximum": 65535, + "description": "The port number" + }, + "protocol": { + "type": "string", + "enum": ["http", "https"], + "description": "The protocol to use (optional, defaults to http)" + }, + "sni": { + "type": "string", + "description": "Server Name Indication for HTTPS connections" + } + }, + "additionalProperties": false + } + }, + "required": ["name", "configurations"], + "additionalProperties": false + }`) +} diff --git a/plugins/golang-filter/mcp-server/servers/higress/higress-ops/.keep b/plugins/golang-filter/mcp-server/servers/higress/higress-ops/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/.keep b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/golang-filter/mcp-server/servers/higress/types.go b/plugins/golang-filter/mcp-server/servers/higress/types.go new file mode 100644 index 000000000..e7d0eb3f8 --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/types.go @@ -0,0 +1,8 @@ +package higress + +// APIResponse represents the standard Higress API response format +type APIResponse[T any] struct { + Success bool `json:"success"` + Message string `json:"message,omitempty"` + Data T `json:"data,omitempty"` +}