diff --git a/plugins/golang-filter/mcp-server/config.go b/plugins/golang-filter/mcp-server/config.go index de9ddb614..d18c11025 100644 --- a/plugins/golang-filter/mcp-server/config.go +++ b/plugins/golang-filter/mcp-server/config.go @@ -6,6 +6,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" + _ "github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/higress/nginx-migration" _ "github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/rag" mcp_session "github.com/alibaba/higress/plugins/golang-filter/mcp-session" "github.com/alibaba/higress/plugins/golang-filter/mcp-session/common" diff --git a/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/.gitignore b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/.gitignore new file mode 100644 index 000000000..af62a7f34 --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/.gitignore @@ -0,0 +1,2 @@ +# 本地配置文件 +config/rag.json \ No newline at end of file diff --git a/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/.keep b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/Makefile b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/Makefile new file mode 100644 index 000000000..96809af08 --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/Makefile @@ -0,0 +1,59 @@ +.PHONY: all build standalone clean test help + +# 默认目标 +all: standalone + +# build 别名(指向 standalone) +build: standalone + +# 编译独立模式 +standalone: + @echo "编译独立模式..." + @cd standalone/cmd && go build -o ../../nginx-migration-mcp + @echo "独立模式编译完成: ./nginx-migration-mcp" + +# 清理编译产物 +clean: + @echo "清理编译产物..." + @rm -f nginx-migration-mcp + @echo "清理完成" + +# 运行测试 +test: + @echo "运行测试..." + @go test ./... + @echo "测试完成" + + +# 安装依赖 +deps: + @echo "安装依赖..." + @go mod tidy + @echo "依赖安装完成" + +# 格式化代码 +fmt: + @echo "格式化代码..." + @go fmt ./... + @echo "代码格式化完成" + +# 显示帮助 +help: + @echo "Nginx Migration MCP Server - Makefile" + @echo "" + @echo "可用目标:" + @echo " make - 编译独立模式(默认)" + @echo " make build - 编译独立模式" + @echo " make standalone - 编译独立模式" + @echo " make test - 运行测试" + @echo " make clean - 清理编译产物" + @echo " make deps - 安装依赖" + @echo " make fmt - 格式化代码" + @echo " make help - 显示此帮助信息" + @echo "" + @echo "目录结构:" + @echo " cmd/standalone/ - 独立模式入口" + @echo " internal/standalone/ - 独立模式实现" + @echo " integration/ - Higress MCP 框架集成" + @echo " tools/ - 共享核心逻辑" + diff --git a/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/QUICKSTART.md b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/QUICKSTART.md new file mode 100644 index 000000000..a10fbbe6c --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/QUICKSTART.md @@ -0,0 +1,90 @@ +# Nginx Migration MCP 快速开始 + + +### 1. 构建服务器 + +```bash +cd /path/to/higress/plugins/golang-filter/mcp-server/servers/higress/nginx-migration +make build +``` + +### 2. 配置 MCP 客户端 + +在 MCP 客户端配置文件中添加(以 Cursor 为例): + +**位置**: `~/.cursor/config/mcp_settings.json` 或 Cursor 设置中的 MCP 配置 + +```json +{ + "mcpServers": { + "nginx-migration": { + "command": "/path/to/nginx-migration/nginx-migration-mcp", + "args": [] + } + } +} +``` + +## 可用工具 + +### Nginx 配置转换 + + `parse_nginx_config` | 解析和分析 Nginx 配置文件 | + `convert_to_higress` | 转换为 Higress HTTPRoute 和 Service | + +### Lua 插件迁移 + + + `convert_lua_to_wasm` | 一键转换 Lua 脚本为 WASM 插件 | + `analyze_lua_plugin` | 分析 Lua 插件兼容性 | + `generate_conversion_hints` | 生成 API 映射和转换提示 | + `validate_wasm_code` | 验证 Go WASM 代码 | + `generate_deployment_config` | 生成部署配置包 | + +## 使用示例 + +### 示例 1:转换 Nginx 配置 + +``` +我有一个 Nginx 配置,帮我转换为 Higress HTTPRoute +``` + +HOST LLM 会自动调用 `convert_to_higress` 工具完成转换。 + +### 示例 2:快速转换 Lua 插件 + +``` +将这个 Lua 限流插件转换为 Higress WASM 插件: +[粘贴 Lua 代码] +``` + +HOST LLM 会调用 `convert_lua_to_wasm` 工具自动转换。 + +### 示例 3:使用工具链精细转换 + +``` +分析这个 Lua 插件的兼容性: +[粘贴 Lua 代码] +``` + +然后按照工具链流程: +1. LLM 调用 `analyze_lua_plugin` 分析 +2. LLM 调用 `generate_conversion_hints` 获取转换提示 +3. LLM 基于提示生成 Go WASM 代码 +4. LLM 调用 `validate_wasm_code` 验证代码 +5. LLM 调用 `generate_deployment_config` 生成部署配置 + +## 调试 + +启用调试日志: + +```bash +DEBUG=true ./nginx-migration-mcp +``` + +查看工具列表: + +```bash +echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | ./nginx-migration-mcp +``` + diff --git a/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/README.md b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/README.md new file mode 100644 index 000000000..5a084ab8c --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/README.md @@ -0,0 +1,319 @@ +# Nginx Migration MCP Server + +一个用于将 Nginx 配置和 Lua 插件迁移到 Higress 的 MCP 服务器。 + +## 功能特性 + +### 配置转换工具 +- **parse_nginx_config** - 解析和分析 Nginx 配置文件 +- **convert_to_higress** - 将 Nginx 配置转换为 Higress Ingress(主要方式)或 HTTPRoute(可选) + +### Lua 插件迁移工具链 + +#### 快速转换模式 +- **convert_lua_to_wasm** - 一键将 Lua 脚本转换为 WASM 插件 + +#### LLM 辅助工具链(精细化控制) +1. **analyze_lua_plugin** - 分析 Lua 插件兼容性 +2. **generate_conversion_hints** - 生成详细的代码转换提示和 API 映射 +3. **validate_wasm_code** - 验证生成的 Go WASM 代码 +4. **generate_deployment_config** - 生成完整的部署配置包 + +## 快速开始 + +### 构建 + +```bash +make build +``` + +构建后会生成 `nginx-migration-mcp` 可执行文件。 + +### 基础配置(无需知识库) + +**默认模式**:工具可以直接使用,基于内置规则生成转换建议。 + +在 MCP 客户端(如 Cursor)的配置文件中添加: + +```json +{ + "mcpServers": { + "nginx-migration": { + "command": "/path/to/nginx-migration-mcp", + "args": [] + } + } +} +``` + +### 进阶配置(启用 RAG 知识库) + +RAG(检索增强生成)功能通过阿里云百炼集成 Higress 官方文档知识库,提供更准确的转换建议和 API 映射。 + +#### 适用场景 + +启用 RAG 后,以下工具将获得增强: +- **generate_conversion_hints** - 提供基于官方文档的 API 映射和代码示例 +- **validate_wasm_code** - 基于最佳实践验证代码质量 +- **query_knowledge_base** - 直接查询 Higress 官方文档 + +#### 配置步骤 + +**步骤 1:获取阿里云百炼凭证** + +1. 访问 [阿里云百炼控制台](https://bailian.console.aliyun.com/) +2. 创建或选择一个应用空间,获取 **业务空间 ID** (`workspace_id`) +3. 创建知识库并导入 Higress 文档,获取 **知识库 ID** (`knowledge_base_id`) +4. 在 [阿里云 RAM 控制台](https://ram.console.aliyun.com/manage/ak) 创建 AccessKey + - 获取 **AccessKey ID** (`access_key_id`) + - 获取 **AccessKey Secret** (`access_key_secret`) + +> **安全提示**:请妥善保管 AccessKey,避免泄露。建议使用 RAM 子账号并授予最小权限。 + +**步骤 2:复制配置文件** +```bash +cp config/rag.json.example config/rag.json +``` + +**步骤 3:编辑配置文件** + +有两种配置方式: + +**方式 1:配置 rag.config** +```json + + +```json +{ + "rag": { + "enabled": true, + "provider": "bailian", + "endpoint": "bailian.cn-beijing.aliyuncs.com", + "workspace_id": "${WORKSPACE_ID}", + "knowledge_base_id": "${INDEX_ID}", + "access_key_id": "${ALIBABA_CLOUD_ACCESS_KEY_ID}", + "access_key_secret": "${ALIBABA_CLOUD_ACCESS_KEY_SECRET}" + } +} +``` + +#### 高级配置项 + +完整的配置选项(可选): + +```json +{ + "rag": { + "enabled": true, + + // === 必填:API 配置 === + "provider": "bailian", + "endpoint": "bailian.cn-beijing.aliyuncs.com", + "workspace_id": "llm-xxx", + "knowledge_base_id": "idx-xxx", + "access_key_id": "LTAI5t...", + "access_key_secret": "your-secret", + + // === 可选:检索配置 === + "context_mode": "full", // 上下文模式: full | summary | highlights + "max_context_length": 4000, // 最大上下文长度(字符) + "default_top_k": 3, // 默认返回文档数量 + "similarity_threshold": 0.7, // 相似度阈值(0-1) + + // === 可选:性能配置 === + "enable_cache": true, // 启用查询缓存 + "cache_ttl": 3600, // 缓存过期时间(秒) + "cache_max_size": 1000, // 最大缓存条目数 + "timeout": 10, // 请求超时(秒) + "max_retries": 3, // 最大重试次数 + "retry_delay": 1, // 重试间隔(秒) + + // === 可选:降级策略 === + "fallback_on_error": true, // RAG 失败时降级到基础模式 + + // === 可选:工具级配置 === + "tools": { + "generate_conversion_hints": { + "use_rag": true, // 为此工具启用 RAG + "context_mode": "full", + "top_k": 3 + }, + "validate_wasm_code": { + "use_rag": true, + "context_mode": "highlights", + "top_k": 2 + } + }, + + // === 可选:调试配置 === + "debug": true, // 启用调试日志 + "log_queries": true // 记录所有查询 + } +} +``` + +#### 验证配置 + +启动服务后,检查日志输出: + +```bash +# 正常启用 RAG +✓ RAG enabled: bailian (endpoint: bailian.cn-beijing.aliyuncs.com) +✓ Knowledge base: idx-xxx +✓ Cache enabled (TTL: 3600s, Max: 1000) + +# RAG 未启用 +⚠ RAG disabled, using rule-based conversion only +``` + +## 使用示例 + +### 转换 Nginx 配置 + +使用 `convert_to_higress` 工具,传入 Nginx 配置内容: +- **默认**:生成 Kubernetes Ingress 和 Service 资源 +- **可选**:设置 `use_gateway_api=true` 生成 Gateway API HTTPRoute(需确认已启用) + + +### 迁移 Lua 插件 + +**方式一:快速转换** + +使用 `convert_lua_to_wasm` 工具一键转换 Lua 脚本为 WASM 插件。 + +**方式二:AI 辅助工具链** + +1. 使用 `analyze_lua_plugin` 分析 Lua 代码 +2. 使用 `generate_conversion_hints` 获取转换提示和 API 映射(可启用 RAG 增强) +3. AI 根据提示生成 Go WASM 代码 +4. 使用 `validate_wasm_code` 验证生成的代码(可启用 RAG 增强) +5. 使用 `generate_deployment_config` 生成部署配置 + +推荐使用工具链方式处理复杂插件,可获得更好的转换质量和 AI 辅助。 + +### 查询知识库(需启用 RAG) + +使用 `query_knowledge_base` 工具直接查询 Higress 文档: + +```javascript +// 查询 Lua API 迁移方法 +query_knowledge_base({ + "query": "ngx.req.get_headers 在 Higress 中怎么实现?", + "scenario": "lua_migration", + "top_k": 5 +}) + +// 查询插件配置方法 +query_knowledge_base({ + "query": "Higress 限流插件配置", + "scenario": "config_conversion", + "top_k": 3 +}) +``` + + +## 项目结构 + +``` +nginx-migration/ +├── config/ # 配置文件 +│ ├── rag.json.example # RAG 配置示例 +│ └── rag.json # RAG 配置(需自行创建) +│ +├── integration/ # Higress 集成模式(MCP 集成) +│ ├── server.go # MCP 服务器注册与初始化 +│ └── mcptools/ # MCP 工具实现 +│ ├── adapter.go # MCP 工具适配器 +│ ├── context.go # 迁移上下文管理 +│ ├── nginx_tools.go # Nginx 配置转换工具 +│ ├── lua_tools.go # Lua 插件迁移工具 +│ ├── tool_chain.go # 工具链实现(分析、验证、部署) +│ └── rag_integration.go # RAG 知识库集成 +│ +├── standalone/ # 独立模式(可独立运行) +│ ├── cmd/ +│ │ └── main.go # 独立模式入口 +│ ├── server.go # 独立模式 MCP 服务器 +│ ├── config.go # 配置加载 +│ └── types.go # 类型定义 +│ +├── internal/ # 内部实现包 +│ ├── rag/ # RAG 功能实现 +│ │ ├── config.go # RAG 配置结构 +│ │ ├── client.go # 百炼 API 客户端 +│ │ └── manager.go # RAG 管理器(查询、缓存) +│ └── standalone/ # 独立模式内部实现 +│ └── server.go # 独立服务器逻辑 +│ +├── tools/ # 核心转换逻辑(共享库) +│ ├── mcp_tools.go # MCP 工具定义和注册 +│ ├── nginx_parser.go # Nginx 配置解析器 +│ ├── lua_converter.go # Lua 到 WASM 转换器 +│ └── tool_chain.go # 工具链核心实现 +│ +├── docs/ # 文档目录 +│ +├── mcp-tools.json # MCP 工具元数据定义 +├── go.mod # Go 模块依赖 +├── go.sum # Go 模块校验和 +├── Makefile # 构建脚本 +│ +├── README.md # 项目说明文档 +├── QUICKSTART.md # 快速开始指南 +├── QUICK_TEST.md # 快速测试指南 +├── TEST_EXAMPLES.md # 测试示例 +└── CHANGELOG_INGRESS_PRIORITY.md # Ingress 优先级变更记录 +``` + +### 目录说明 + +#### 核心模块 + +- **`integration/`** - Higress 集成模式 + - 作为 Higress MCP 服务器的一部分运行 + - 提供完整的 MCP 工具集成 + - 支持 RAG 知识库增强 + +- **`standalone/`** - 独立模式 + - 可独立运行的 MCP 服务器 + - 适合本地开发和测试 + - 支持相同的工具功能 + +- **`tools/`** - 核心转换逻辑 + - 独立于运行模式的转换引擎 + - 包含 Nginx 解析、Lua 转换等核心功能 + - 可被集成模式和独立模式复用 + +- **`internal/rag/`** - RAG 功能实现 + - 阿里云百炼 API 客户端 + - 知识库查询和结果处理 + - 缓存管理和性能优化 + + +#### 配置文件 + +- **`config/rag.json`** - RAG 功能配置(需从 example 复制并填写凭证) +- **`mcp-tools.json`** - MCP 工具元数据定义(工具描述、参数 schema) + +## 开发 + +### 构建命令 + +```bash +# 编译 +make build + +# 清理 +make clean + +# 格式化代码 +make fmt + +# 运行测试 +make test + +# 查看帮助 +make help +``` + + diff --git a/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/config/rag.json.example b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/config/rag.json.example new file mode 100644 index 000000000..c991798f4 --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/config/rag.json.example @@ -0,0 +1,46 @@ +{ + "rag": { + "enabled": true, + "provider": "bailian", + "endpoint": "bailian.cn-beijing.aliyuncs.com", + "workspace_id": "${WORKSPACE_ID}", + "knowledge_base_id": "${INDEX_ID}", + "access_key_id": "${ALIBABA_CLOUD_ACCESS_KEY_ID}", + "access_key_secret": "${ALIBABA_CLOUD_ACCESS_KEY_SECRET}", + + "context_mode": "full", + "max_context_length": 4000, + "default_top_k": 3, + "similarity_threshold": 0.7, + + "enable_cache": true, + "cache_ttl": 3600, + "cache_max_size": 1000, + + "timeout": 10, + "max_retries": 3, + "retry_delay": 1, + + "fallback_on_error": true, + + "tools": { + "generate_conversion_hints": { + "use_rag": true, + "context_mode": "full", + "top_k": 3 + }, + "validate_wasm_code": { + "use_rag": true, + "context_mode": "highlights", + "top_k": 2 + }, + "convert_lua_to_wasm": { + "use_rag": false + } + }, + + "debug": true, + "log_queries": true + } +} + diff --git a/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/go.mod b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/go.mod new file mode 100644 index 000000000..b4daf074b --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/go.mod @@ -0,0 +1,33 @@ +module nginx-migration-mcp + +go 1.23 + +toolchain go1.24.9 + +require ( + github.com/alibaba/higress/plugins/golang-filter v0.0.0-20251023035326-7ea739292dea + github.com/alibabacloud-go/bailian-20231229/v2 v2.5.0 + github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13 + github.com/alibabacloud-go/tea v1.3.13 + github.com/alibabacloud-go/tea-utils/v2 v2.0.7 + github.com/envoyproxy/envoy v1.36.2 +) + +require ( + github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect + github.com/alibabacloud-go/debug v1.0.1 // indirect + github.com/aliyun/credentials-go v1.4.5 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/clbanning/mxj/v2 v2.7.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/go-redis/redis/v8 v8.11.5 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mark3labs/mcp-go v0.12.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect + golang.org/x/net v0.34.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect +) diff --git a/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/integration/mcptools/adapter.go b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/integration/mcptools/adapter.go new file mode 100644 index 000000000..9acecaf3c --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/integration/mcptools/adapter.go @@ -0,0 +1,60 @@ +//go:build higress_integration +// +build higress_integration + +package mcptools + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/alibaba/higress/plugins/golang-filter/mcp-session/common" + "github.com/mark3labs/mcp-go/mcp" +) + +// SimpleToolHandler is a simplified handler function that takes arguments and returns a string result +type SimpleToolHandler func(args map[string]interface{}) (string, error) + +// AdaptSimpleHandler converts a SimpleToolHandler to an MCP ToolHandlerFunc +func AdaptSimpleHandler(handler SimpleToolHandler) common.ToolHandlerFunc { + return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + // Extract arguments + var args map[string]interface{} + if request.Params.Arguments != nil { + args = request.Params.Arguments + } else { + args = make(map[string]interface{}) + } + + // Call the simple handler + result, err := handler(args) + if err != nil { + return nil, fmt.Errorf("tool execution failed: %w", err) + } + + // Return MCP result + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.TextContent{ + Type: "text", + Text: result, + }, + }, + }, nil + } +} + +// RegisterSimpleTool registers a tool with a simplified handler +func RegisterSimpleTool( + server *common.MCPServer, + name string, + description string, + inputSchema map[string]interface{}, + handler SimpleToolHandler, +) { + // Create tool with schema + schemaBytes, _ := json.Marshal(inputSchema) + + tool := mcp.NewToolWithRawSchema(name, description, schemaBytes) + server.AddTool(tool, AdaptSimpleHandler(handler)) +} diff --git a/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/integration/mcptools/context.go b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/integration/mcptools/context.go new file mode 100644 index 000000000..7b55f1519 --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/integration/mcptools/context.go @@ -0,0 +1,57 @@ +//go:build higress_integration +// +build higress_integration + +package mcptools + +import ( + "log" + "nginx-migration-mcp/internal/rag" +) + +// MigrationContext holds the configuration context for migration operations +type MigrationContext struct { + GatewayName string + GatewayNamespace string + DefaultNamespace string + DefaultHostname string + RoutePrefix string + ServicePort int + TargetPort int + RAGManager *rag.RAGManager // RAG 管理器 +} + +// NewDefaultMigrationContext creates a MigrationContext with default values +func NewDefaultMigrationContext() *MigrationContext { + return &MigrationContext{ + GatewayName: "higress-gateway", + GatewayNamespace: "higress-system", + DefaultNamespace: "default", + DefaultHostname: "example.com", + RoutePrefix: "nginx-migrated", + ServicePort: 80, + TargetPort: 8080, + } +} + +// NewMigrationContextWithRAG creates a MigrationContext with RAG support +func NewMigrationContextWithRAG(ragConfigPath string) *MigrationContext { + ctx := NewDefaultMigrationContext() + + // 加载 RAG 配置 + config, err := rag.LoadRAGConfig(ragConfigPath) + if err != nil { + log.Printf("⚠️ Failed to load RAG config: %v, RAG will be disabled", err) + config = &rag.RAGConfig{Enabled: false} + } + + // 创建 RAG 管理器 + ctx.RAGManager = rag.NewRAGManager(config) + + if ctx.RAGManager.IsEnabled() { + log.Println("✅ MigrationContext: RAG enabled") + } else { + log.Println("📖 MigrationContext: RAG disabled, using rule-based approach") + } + + return ctx +} diff --git a/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/integration/mcptools/lua_tools.go b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/integration/mcptools/lua_tools.go new file mode 100644 index 000000000..409bbc9ae --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/integration/mcptools/lua_tools.go @@ -0,0 +1,217 @@ +//go:build higress_integration +// +build higress_integration + +package mcptools + +import ( + "fmt" + "log" + "strings" + + "nginx-migration-mcp/internal/rag" + "nginx-migration-mcp/tools" + + "github.com/alibaba/higress/plugins/golang-filter/mcp-session/common" +) + +// RegisterLuaPluginTools registers Lua plugin analysis and conversion tools +func RegisterLuaPluginTools(server *common.MCPServer, ctx *MigrationContext) { + RegisterSimpleTool( + server, + "analyze_lua_plugin", + "分析 Nginx Lua 插件的兼容性,识别使用的 API 和潜在迁移问题", + map[string]interface{}{ + "type": "object", + "properties": map[string]interface{}{ + "lua_code": map[string]interface{}{ + "type": "string", + "description": "Nginx Lua 插件代码", + }, + }, + "required": []string{"lua_code"}, + }, + func(args map[string]interface{}) (string, error) { + return analyzeLuaPlugin(args, ctx) + }, + ) + + RegisterSimpleTool( + server, + "convert_lua_to_wasm", + "一键将 Nginx Lua 脚本转换为 Higress WASM 插件,自动生成 Go 代码和配置", + map[string]interface{}{ + "type": "object", + "properties": map[string]interface{}{ + "lua_code": map[string]interface{}{ + "type": "string", + "description": "要转换的 Nginx Lua 插件代码", + }, + "plugin_name": map[string]interface{}{ + "type": "string", + "description": "生成的 WASM 插件名称 (小写字母和连字符)", + }, + }, + "required": []string{"lua_code", "plugin_name"}, + }, + func(args map[string]interface{}) (string, error) { + return convertLuaToWasm(args, ctx) + }, + ) +} + +func analyzeLuaPlugin(args map[string]interface{}, ctx *MigrationContext) (string, error) { + luaCode, ok := args["lua_code"].(string) + if !ok { + return "", fmt.Errorf("missing or invalid lua_code parameter") + } + + // Analyze Lua features (基于规则) + features := []string{} + warnings := []string{} + detectedAPIs := []string{} + + if strings.Contains(luaCode, "ngx.var") { + features = append(features, "- ngx.var - Nginx变量") + detectedAPIs = append(detectedAPIs, "ngx.var") + } + if strings.Contains(luaCode, "ngx.req") { + features = append(features, "- ngx.req - 请求API") + detectedAPIs = append(detectedAPIs, "ngx.req") + } + if strings.Contains(luaCode, "ngx.exit") { + features = append(features, "- ngx.exit - 请求终止") + detectedAPIs = append(detectedAPIs, "ngx.exit") + } + if strings.Contains(luaCode, "ngx.shared") { + features = append(features, "- ngx.shared - 共享字典 (警告)") + warnings = append(warnings, "共享字典需要外部缓存替换") + detectedAPIs = append(detectedAPIs, "ngx.shared") + } + if strings.Contains(luaCode, "ngx.location.capture") { + features = append(features, "- ngx.location.capture - 内部请求 (警告)") + warnings = append(warnings, "需要改为HTTP客户端调用") + detectedAPIs = append(detectedAPIs, "ngx.location.capture") + } + + compatibility := "full" + if len(warnings) > 0 { + compatibility = "partial" + } + if len(warnings) > 2 { + compatibility = "manual" + } + + // === RAG 增强:查询知识库获取转换建议 === + var ragContext *rag.RAGContext + if ctx.RAGManager != nil && ctx.RAGManager.IsEnabled() && len(detectedAPIs) > 0 { + query := fmt.Sprintf("Nginx Lua API %s 在 Higress WASM 中的转换方法和最佳实践", strings.Join(detectedAPIs, ", ")) + var err error + ragContext, err = ctx.RAGManager.QueryForTool("analyze_lua_plugin", query, "lua_migration") + if err != nil { + log.Printf("⚠️ RAG query failed for analyze_lua_plugin: %v", err) + } + } + + // 构建结果 + var result strings.Builder + + // RAG 上下文(如果有) + if ragContext != nil && ragContext.Enabled && len(ragContext.Documents) > 0 { + result.WriteString("📚 知识库参考资料:\n\n") + result.WriteString(ragContext.FormatContextForAI()) + result.WriteString("\n") + } + + // 基于规则的分析 + warningsText := "无" + if len(warnings) > 0 { + warningsText = strings.Join(warnings, "\n") + } + + result.WriteString(fmt.Sprintf(`Lua插件兼容性分析 + +检测特性: +%s + +兼容性警告: +%s + +兼容性级别: %s + +迁移建议:`, strings.Join(features, "\n"), warningsText, compatibility)) + + switch compatibility { + case "full": + result.WriteString("\n- 可直接迁移到WASM插件") + case "partial": + result.WriteString("\n- 需要部分重构") + case "manual": + result.WriteString("\n- 需要手动重写") + } + + return result.String(), nil +} + +func convertLuaToWasm(args map[string]interface{}, ctx *MigrationContext) (string, error) { + luaCode, ok := args["lua_code"].(string) + if !ok { + return "", fmt.Errorf("missing or invalid lua_code parameter") + } + + pluginName, ok := args["plugin_name"].(string) + if !ok { + return "", fmt.Errorf("missing or invalid plugin_name parameter") + } + + // 分析Lua脚本 + analyzer := tools.AnalyzeLuaScript(luaCode) + + // === RAG 增强:查询转换模式和代码示例 === + var ragContext *rag.RAGContext + if ctx.RAGManager != nil && ctx.RAGManager.IsEnabled() && len(analyzer.Features) > 0 { + // 提取特性列表 + featureList := []string{} + for feature := range analyzer.Features { + featureList = append(featureList, feature) + } + + query := fmt.Sprintf("将使用了 %s 的 Nginx Lua 插件转换为 Higress WASM Go 插件的代码示例", + strings.Join(featureList, ", ")) + var err error + ragContext, err = ctx.RAGManager.QueryForTool("convert_lua_to_wasm", query, "lua_to_wasm") + if err != nil { + log.Printf("⚠️ RAG query failed for convert_lua_to_wasm: %v", err) + } + } + + // 转换为WASM插件 + result, err := tools.ConvertLuaToWasm(analyzer, pluginName) + if err != nil { + return "", fmt.Errorf("conversion failed: %w", err) + } + + // 构建响应 + var response strings.Builder + + // RAG 上下文(如果有) + if ragContext != nil && ragContext.Enabled && len(ragContext.Documents) > 0 { + response.WriteString("📚 知识库代码示例:\n\n") + response.WriteString(ragContext.FormatContextForAI()) + response.WriteString("\n---\n\n") + } + + response.WriteString(fmt.Sprintf(`Go 代码: +%s + +WasmPlugin 配置: +%s + +复杂度: %s, 特性: %d, 警告: %d`, + result.GoCode, + result.WasmPluginYAML, + analyzer.Complexity, + len(analyzer.Features), + len(analyzer.Warnings))) + + return response.String(), nil +} diff --git a/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/integration/mcptools/nginx_tools.go b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/integration/mcptools/nginx_tools.go new file mode 100644 index 000000000..1649be799 --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/integration/mcptools/nginx_tools.go @@ -0,0 +1,491 @@ +//go:build higress_integration +// +build higress_integration + +package mcptools + +import ( + "encoding/json" + "fmt" + "log" + "nginx-migration-mcp/internal/rag" + "nginx-migration-mcp/tools" + "strings" + + "github.com/alibaba/higress/plugins/golang-filter/mcp-session/common" +) + +// RegisterNginxConfigTools 注册 Nginx 配置分析和转换工具 +func RegisterNginxConfigTools(server *common.MCPServer, ctx *MigrationContext) { + RegisterSimpleTool( + server, + "parse_nginx_config", + "解析和分析 Nginx 配置文件,识别配置结构和复杂度", + map[string]interface{}{ + "type": "object", + "properties": map[string]interface{}{ + "config_content": map[string]interface{}{ + "type": "string", + "description": "Nginx 配置文件内容", + }, + }, + "required": []string{"config_content"}, + }, + func(args map[string]interface{}) (string, error) { + return parseNginxConfig(args, ctx) + }, + ) + + RegisterSimpleTool( + server, + "convert_to_higress", + "将 Nginx 配置转换为 Higress Ingress 和 Service 资源(主要方式)或 HTTPRoute(可选)", + map[string]interface{}{ + "type": "object", + "properties": map[string]interface{}{ + "config_content": map[string]interface{}{ + "type": "string", + "description": "Nginx 配置文件内容", + }, + "namespace": map[string]interface{}{ + "type": "string", + "description": "目标 Kubernetes 命名空间", + "default": "default", + }, + "use_gateway_api": map[string]interface{}{ + "type": "boolean", + "description": "是否使用 Gateway API (HTTPRoute)。默认 false,使用 Ingress", + "default": false, + }, + }, + "required": []string{"config_content"}, + }, + func(args map[string]interface{}) (string, error) { + return convertToHigress(args, ctx) + }, + ) +} + +func parseNginxConfig(args map[string]interface{}, ctx *MigrationContext) (string, error) { + configContent, ok := args["config_content"].(string) + if !ok { + return "", fmt.Errorf("missing or invalid config_content parameter") + } + + // Simple analysis + serverCount := strings.Count(configContent, "server {") + locationCount := strings.Count(configContent, "location") + hasSSL := strings.Contains(configContent, "ssl") + hasProxy := strings.Contains(configContent, "proxy_pass") + hasRewrite := strings.Contains(configContent, "rewrite") + + complexity := "Simple" + if serverCount > 1 || (hasRewrite && hasSSL) { + complexity = "Complex" + } else if hasRewrite || hasSSL { + complexity = "Medium" + } + + // 收集配置特性用于 RAG 查询 + features := []string{} + if hasProxy { + features = append(features, "反向代理") + } + if hasRewrite { + features = append(features, "URL重写") + } + if hasSSL { + features = append(features, "SSL配置") + } + + // === RAG 增强:查询 Nginx 配置迁移最佳实践 === + var ragContext *rag.RAGContext + if ctx.RAGManager != nil && ctx.RAGManager.IsEnabled() && len(features) > 0 { + query := fmt.Sprintf("Nginx %s 迁移到 Higress 的配置方法和最佳实践", strings.Join(features, "、")) + var err error + ragContext, err = ctx.RAGManager.QueryForTool("parse_nginx_config", query, "nginx_migration") + if err != nil { + log.Printf(" RAG query failed for parse_nginx_config: %v", err) + } + } + + // 构建分析结果 + var result strings.Builder + + // RAG 上下文(如果有) + if ragContext != nil && ragContext.Enabled && len(ragContext.Documents) > 0 { + result.WriteString("📚 知识库迁移指南:\n\n") + result.WriteString(ragContext.FormatContextForAI()) + result.WriteString("\n---\n\n") + } + + result.WriteString(fmt.Sprintf(`Nginx配置分析结果 + +基础信息: +- Server块: %d个 +- Location块: %d个 +- SSL配置: %t +- 反向代理: %t +- URL重写: %t + +复杂度: %s + +迁移建议:`, serverCount, locationCount, hasSSL, hasProxy, hasRewrite, complexity)) + + if hasProxy { + result.WriteString("\n- 反向代理将转换为Ingress backend配置") + } + if hasRewrite { + result.WriteString("\n- URL重写将使用Higress注解 (higress.io/rewrite-target)") + } + if hasSSL { + result.WriteString("\n- SSL配置将转换为Ingress TLS配置") + } + + return result.String(), nil +} + +func convertToHigress(args map[string]interface{}, ctx *MigrationContext) (string, error) { + configContent, ok := args["config_content"].(string) + if !ok { + return "", fmt.Errorf("missing or invalid config_content parameter") + } + + namespace := ctx.DefaultNamespace + if ns, ok := args["namespace"].(string); ok && ns != "" { + namespace = ns + } + + // 检查是否使用 Gateway API + useGatewayAPI := false + if val, ok := args["use_gateway_api"].(bool); ok { + useGatewayAPI = val + } + + // === 使用增强的解析器解析 Nginx 配置 === + nginxConfig, err := tools.ParseNginxConfig(configContent) + if err != nil { + return "", fmt.Errorf("failed to parse Nginx config: %v", err) + } + + // 分析配置 + analysis := tools.AnalyzeNginxConfig(nginxConfig) + + // === RAG 增强:查询转换示例和最佳实践 === + var ragContext string + if ctx.RAGManager != nil && ctx.RAGManager.IsEnabled() { + // 构建查询关键词 + queryBuilder := []string{"Nginx 配置转换到 Higress"} + + if useGatewayAPI { + queryBuilder = append(queryBuilder, "Gateway API HTTPRoute") + } else { + queryBuilder = append(queryBuilder, "Kubernetes Ingress") + } + + // 根据特性添加查询关键词 + if analysis.Features["ssl"] { + queryBuilder = append(queryBuilder, "SSL TLS 证书配置") + } + if analysis.Features["rewrite"] { + queryBuilder = append(queryBuilder, "URL 重写 rewrite 规则") + } + if analysis.Features["redirect"] { + queryBuilder = append(queryBuilder, "重定向 redirect") + } + if analysis.Features["header_manipulation"] { + queryBuilder = append(queryBuilder, "请求头 响应头处理") + } + if len(nginxConfig.Upstreams) > 0 { + queryBuilder = append(queryBuilder, "负载均衡 upstream") + } + + queryString := strings.Join(queryBuilder, " ") + log.Printf("🔍 RAG Query: %s", queryString) + + ragResult, err := ctx.RAGManager.QueryForTool( + "convert_to_higress", + queryString, + "nginx_to_higress", + ) + + if err == nil && ragResult.Enabled && len(ragResult.Documents) > 0 { + log.Printf("✅ RAG: Found %d documents for conversion", len(ragResult.Documents)) + ragContext = "\n\n## 📚 参考文档(来自知识库)\n\n" + ragResult.FormatContextForAI() + } else { + if err != nil { + log.Printf("⚠️ RAG query failed: %v", err) + } + } + } + + // === 将配置数据转换为 JSON 供 AI 使用 === + configJSON, _ := json.MarshalIndent(nginxConfig, "", " ") + analysisJSON, _ := json.MarshalIndent(analysis, "", " ") + + // === 构建返回消息 === + var result strings.Builder + + result.WriteString(fmt.Sprintf(`📋 Nginx 配置解析完成 + +## 配置概览 +- Server 块: %d +- Location 块: %d +- 域名: %d 个 +- 复杂度: %s +- 目标格式: %s +- 命名空间: %s + +## 检测到的特性 +%s + +## 迁移建议 +%s +%s + +--- + +## Nginx 配置结构 + +`+"```json"+` +%s +`+"```"+` + +## 分析结果 + +`+"```json"+` +%s +`+"```"+` +%s +`, + analysis.ServerCount, + analysis.LocationCount, + analysis.DomainCount, + analysis.Complexity, + func() string { + if useGatewayAPI { + return "Gateway API (HTTPRoute)" + } + return "Kubernetes Ingress" + }(), + namespace, + formatFeaturesForOutput(analysis.Features), + formatSuggestionsForOutput(analysis.Suggestions), + func() string { + if ragContext != "" { + return "\n\n✅ 已加载知识库参考文档" + } + return "" + }(), + string(configJSON), + string(analysisJSON), + ragContext, + )) + + return result.String(), nil +} + +// generateIngressConfig 生成 Ingress 资源配置(主要方式) +func generateIngressConfig(ingressName, namespace, hostname, serviceName string, ctx *MigrationContext) string { + return fmt.Sprintf(`转换后的Higress配置(使用 Ingress - 推荐方式) + +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: %s + namespace: %s + annotations: + higress.io/migrated-from: "nginx" + higress.io/ingress.class: "higress" +spec: + ingressClassName: higress + rules: + - host: %s + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: %s + port: + number: %d + +--- +apiVersion: v1 +kind: Service +metadata: + name: %s + namespace: %s +spec: + selector: + app: backend + ports: + - port: %d + targetPort: %d + protocol: TCP + +转换完成 + +应用步骤: +1. 保存为 higress-config.yaml +2. 执行: kubectl apply -f higress-config.yaml +3. 验证: kubectl get ingress -n %s + +说明: +- 使用 Ingress 是 Higress 的主要使用方式,兼容性最好 +- 如需使用 Gateway API (HTTPRoute),请设置参数 use_gateway_api=true`, + ingressName, namespace, + hostname, + serviceName, ctx.ServicePort, + serviceName, namespace, + ctx.ServicePort, ctx.TargetPort, + namespace) +} + +// generateHTTPRouteConfig 生成 HTTPRoute 资源配置(备用选项) +func generateHTTPRouteConfig(routeName, namespace, hostname, serviceName string, ctx *MigrationContext) string { + return fmt.Sprintf(`转换后的Higress配置(使用 Gateway API - 可选方式) + +注意: Gateway API 在 Higress 中默认关闭,使用前需要确认已启用。 + +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: %s + namespace: %s + annotations: + higress.io/migrated-from: "nginx" +spec: + parentRefs: + - name: %s + namespace: %s + hostnames: + - %s + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: %s + port: %d + +--- +apiVersion: v1 +kind: Service +metadata: + name: %s + namespace: %s +spec: + selector: + app: backend + ports: + - port: %d + targetPort: %d + protocol: TCP + +转换完成 + +应用步骤: +1. 确认 Gateway API 已启用: PILOT_ENABLE_GATEWAY_API=true +2. 保存为 higress-config.yaml +3. 执行: kubectl apply -f higress-config.yaml +4. 验证: kubectl get httproute -n %s + +说明: +- Gateway API 是可选功能,默认关闭 +- 推荐使用 Ingress (设置 use_gateway_api=false)`, + routeName, namespace, + ctx.GatewayName, ctx.GatewayNamespace, hostname, + serviceName, ctx.ServicePort, + serviceName, namespace, + ctx.ServicePort, ctx.TargetPort, + namespace) +} + +func generateIngressName(hostname string, ctx *MigrationContext) string { + prefix := "nginx-migrated" + if ctx.RoutePrefix != "" { + prefix = ctx.RoutePrefix + } + + if hostname == "" || hostname == ctx.DefaultHostname { + return fmt.Sprintf("%s-ingress", prefix) + } + // Replace dots and special characters for valid k8s name + safeName := hostname + for _, char := range []string{".", "_", ":"} { + safeName = strings.ReplaceAll(safeName, char, "-") + } + return fmt.Sprintf("%s-%s", prefix, safeName) +} + +func generateRouteName(hostname string, ctx *MigrationContext) string { + prefix := "nginx-migrated" + if ctx.RoutePrefix != "" { + prefix = ctx.RoutePrefix + } + + if hostname == "" || hostname == ctx.DefaultHostname { + return fmt.Sprintf("%s-route", prefix) + } + // Replace dots and special characters for valid k8s name + safeName := hostname + for _, char := range []string{".", "_", ":"} { + safeName = strings.ReplaceAll(safeName, char, "-") + } + return fmt.Sprintf("%s-%s", prefix, safeName) +} + +func generateServiceName(hostname string, ctx *MigrationContext) string { + if hostname == "" || hostname == ctx.DefaultHostname { + return "backend-service" + } + safeName := hostname + for _, char := range []string{".", "_", ":"} { + safeName = strings.ReplaceAll(safeName, char, "-") + } + return fmt.Sprintf("%s-service", safeName) +} + +// formatFeaturesForOutput 格式化特性列表用于输出 +func formatFeaturesForOutput(features map[string]bool) string { + featureNames := map[string]string{ + "ssl": "SSL/TLS 加密", + "proxy": "反向代理", + "rewrite": "URL 重写", + "redirect": "重定向", + "return": "返回指令", + "complex_routing": "复杂路由匹配", + "header_manipulation": "请求头操作", + "response_headers": "响应头操作", + } + + var result []string + for key, enabled := range features { + if enabled { + if name, ok := featureNames[key]; ok { + result = append(result, fmt.Sprintf("- ✅ %s", name)) + } else { + result = append(result, fmt.Sprintf("- ✅ %s", key)) + } + } + } + + if len(result) == 0 { + return "- 基础配置(无特殊特性)" + } + return strings.Join(result, "\n") +} + +// formatSuggestionsForOutput 格式化建议列表用于输出 +func formatSuggestionsForOutput(suggestions []string) string { + if len(suggestions) == 0 { + return "- 无特殊建议" + } + var result []string + for _, s := range suggestions { + result = append(result, fmt.Sprintf("- 💡 %s", s)) + } + return strings.Join(result, "\n") +} diff --git a/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/integration/mcptools/rag_integration.go b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/integration/mcptools/rag_integration.go new file mode 100644 index 000000000..a5db5e3b4 --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/integration/mcptools/rag_integration.go @@ -0,0 +1,318 @@ +// Package mcptools 提供 RAG 集成到 MCP 工具的示例实现 +package mcptools + +import ( + "fmt" + "log" + "strings" + + "nginx-migration-mcp/internal/rag" +) + +// RAGToolContext MCP 工具的 RAG 上下文 +type RAGToolContext struct { + Manager *rag.RAGManager +} + +// NewRAGToolContext 创建 RAG 工具上下文 +func NewRAGToolContext(configPath string) (*RAGToolContext, error) { + // 加载配置 + config, err := rag.LoadRAGConfig(configPath) + if err != nil { + log.Printf("⚠️ Failed to load RAG config: %v, RAG will be disabled", err) + // 创建禁用状态的配置 + config = &rag.RAGConfig{Enabled: false} + } + + // 创建 RAG 管理器 + manager := rag.NewRAGManager(config) + + return &RAGToolContext{ + Manager: manager, + }, nil +} + +// ==================== 工具示例:generate_conversion_hints ==================== + +// GenerateConversionHintsWithRAG 生成转换提示(带 RAG 增强) +func (ctx *RAGToolContext) GenerateConversionHintsWithRAG(analysisResult string, pluginName string) (string, error) { + var result strings.Builder + result.WriteString(fmt.Sprintf("# %s 插件转换指南\n\n", pluginName)) + + // 提取 Nginx APIs(这里简化处理) + nginxAPIs := extractNginxAPIs(analysisResult) + + // === 核心:使用工具级别的 RAG 查询 === + toolName := "generate_conversion_hints" + ragContext, err := ctx.Manager.QueryForTool( + toolName, + fmt.Sprintf("Nginx Lua API %s 在 Higress WASM 中的实现和转换方法", strings.Join(nginxAPIs, ", ")), + "lua_migration", + ) + + if err != nil { + log.Printf("❌ RAG query failed for %s: %v", toolName, err) + // 降级到规则生成 + ragContext = &rag.RAGContext{ + Enabled: false, + Message: fmt.Sprintf("RAG query failed: %v", err), + } + } + + // 添加 RAG 上下文信息(如果有) + if ragContext.Enabled && len(ragContext.Documents) > 0 { + result.WriteString(ragContext.FormatContextForAI()) + } else { + // RAG 未启用或查询失败 + result.WriteString(fmt.Sprintf("> ℹ️ %s\n\n", ragContext.Message)) + result.WriteString("> 使用基于规则的转换指南\n\n") + } + + // 为每个 API 生成转换提示(基于规则) + result.WriteString("## 🔄 API 转换详情\n\n") + for _, api := range nginxAPIs { + result.WriteString(fmt.Sprintf("### %s\n\n", api)) + result.WriteString(generateBasicMapping(api)) + result.WriteString("\n") + } + + // 添加使用建议 + result.WriteString("\n---\n\n") + result.WriteString("## 💡 使用建议\n\n") + if ragContext.Enabled { + result.WriteString("✅ 上述参考文档来自 Higress 官方知识库,请参考这些文档中的示例代码和最佳实践来生成 WASM 插件代码。\n\n") + result.WriteString("建议按照知识库中的示例实现,确保代码符合 Higress 的最佳实践。\n") + } else { + result.WriteString("ℹ️ 当前未启用 RAG 知识库或查询失败,使用基于规则的映射。\n\n") + result.WriteString("建议参考 Higress 官方文档:https://higress.cn/docs/plugins/wasm-go-sdk/\n") + } + + return result.String(), nil +} + +// ==================== 工具示例:validate_wasm_code ==================== + +// ValidateWasmCodeWithRAG 验证 WASM 代码(带 RAG 增强) +func (ctx *RAGToolContext) ValidateWasmCodeWithRAG(goCode string, pluginName string) (string, error) { + var result strings.Builder + result.WriteString(fmt.Sprintf("## 🔍 %s 插件代码验证报告\n\n", pluginName)) + + // 基本验证(始终执行) + basicIssues := validateBasicSyntax(goCode) + apiIssues := validateAPIUsage(goCode) + + if len(basicIssues) > 0 { + result.WriteString("### ⚠️ 语法问题\n\n") + for _, issue := range basicIssues { + result.WriteString(fmt.Sprintf("- %s\n", issue)) + } + result.WriteString("\n") + } + + if len(apiIssues) > 0 { + result.WriteString("### ⚠️ API 使用问题\n\n") + for _, issue := range apiIssues { + result.WriteString(fmt.Sprintf("- %s\n", issue)) + } + result.WriteString("\n") + } + + // === RAG 增强:查询最佳实践 === + toolName := "validate_wasm_code" + ragContext, err := ctx.Manager.QueryForTool( + toolName, + "Higress WASM 插件开发最佳实践 错误处理 性能优化 代码规范", + "best_practice", + ) + + if err != nil { + log.Printf("❌ RAG query failed for %s: %v", toolName, err) + ragContext = &rag.RAGContext{ + Enabled: false, + Message: fmt.Sprintf("RAG query failed: %v", err), + } + } + + // 添加最佳实践建议 + if ragContext.Enabled && len(ragContext.Documents) > 0 { + result.WriteString("### 💡 最佳实践建议(基于知识库)\n\n") + + for i, doc := range ragContext.Documents { + result.WriteString(fmt.Sprintf("#### 建议 %d:%s\n\n", i+1, doc.Title)) + result.WriteString(fmt.Sprintf("**来源**: %s \n", doc.Source)) + if doc.URL != "" { + result.WriteString(fmt.Sprintf("**链接**: %s \n", doc.URL)) + } + result.WriteString("\n") + + // 只展示关键片段(validate 工具通常配置为 highlights 模式) + if len(doc.Highlights) > 0 { + result.WriteString("**关键要点**:\n\n") + for _, h := range doc.Highlights { + result.WriteString(fmt.Sprintf("- %s\n", h)) + } + } else { + result.WriteString("**参考内容**:\n\n") + result.WriteString("```\n") + result.WriteString(doc.Content) + result.WriteString("\n```\n") + } + result.WriteString("\n") + } + + // 基于知识库内容检查当前代码 + suggestions := checkCodeAgainstBestPractices(goCode, ragContext.Documents) + if len(suggestions) > 0 { + result.WriteString("### 📝 针对当前代码的改进建议\n\n") + for _, s := range suggestions { + result.WriteString(fmt.Sprintf("- %s\n", s)) + } + result.WriteString("\n") + } + } else { + result.WriteString("### 💡 基本建议\n\n") + result.WriteString(fmt.Sprintf("> %s\n\n", ragContext.Message)) + result.WriteString(generateBasicValidationSuggestions(goCode)) + } + + // 验证总结 + if len(basicIssues) == 0 && len(apiIssues) == 0 { + result.WriteString("\n---\n\n") + result.WriteString("### ✅ 验证通过\n\n") + result.WriteString("代码基本验证通过,没有发现明显问题。\n") + } + + return result.String(), nil +} + +// ==================== 工具示例:convert_lua_to_wasm ==================== + +// ConvertLuaToWasmWithRAG 快速转换(通常不使用 RAG) +func (ctx *RAGToolContext) ConvertLuaToWasmWithRAG(luaCode string, pluginName string) (string, error) { + // 这个工具在配置中通常设置为 use_rag: false,保持快速响应 + + toolName := "convert_lua_to_wasm" + + // 仍然可以查询,但如果配置禁用则会快速返回 + ragContext, _ := ctx.Manager.QueryForTool( + toolName, + "Lua to WASM conversion examples", + "quick_convert", + ) + + var result strings.Builder + result.WriteString(fmt.Sprintf("# %s 插件转换结果\n\n", pluginName)) + + if ragContext.Enabled { + result.WriteString("> 🚀 使用 RAG 增强转换\n\n") + result.WriteString(ragContext.FormatContextForAI()) + } else { + result.WriteString("> ⚡ 快速转换模式(未启用 RAG)\n\n") + } + + // 执行基于规则的转换 + wasmCode := performRuleBasedConversion(luaCode, pluginName) + result.WriteString("## 生成的 Go WASM 代码\n\n") + result.WriteString("```go\n") + result.WriteString(wasmCode) + result.WriteString("\n```\n") + + return result.String(), nil +} + +// ==================== 辅助函数 ==================== + +func extractNginxAPIs(analysisResult string) []string { + // 简化实现:从分析结果中提取 API + apis := []string{"ngx.req.get_headers", "ngx.say", "ngx.var"} + return apis +} + +func generateBasicMapping(api string) string { + mappings := map[string]string{ + "ngx.req.get_headers": "**Higress WASM**: `proxywasm.GetHttpRequestHeaders()`\n\n示例:\n```go\nheaders, err := proxywasm.GetHttpRequestHeaders()\nif err != nil {\n proxywasm.LogError(\"failed to get headers\")\n return types.ActionContinue\n}\n```", + "ngx.say": "**Higress WASM**: `proxywasm.SendHttpResponse()`", + "ngx.var": "**Higress WASM**: `proxywasm.GetProperty()`", + } + + if mapping, ok := mappings[api]; ok { + return mapping + } + return "映射信息暂未提供,请参考官方文档。" +} + +func validateBasicSyntax(goCode string) []string { + // 简化实现 + issues := []string{} + if !strings.Contains(goCode, "package main") { + issues = append(issues, "缺少 package main 声明") + } + return issues +} + +func validateAPIUsage(goCode string) []string { + // 简化实现 + issues := []string{} + if strings.Contains(goCode, "proxywasm.") && !strings.Contains(goCode, "import") { + issues = append(issues, "使用了 proxywasm API 但未导入相关包") + } + return issues +} + +func checkCodeAgainstBestPractices(goCode string, docs []rag.ContextDocument) []string { + // 简化实现:基于文档内容检查代码 + suggestions := []string{} + + // 检查错误处理 + if !strings.Contains(goCode, "if err != nil") { + for _, doc := range docs { + if strings.Contains(doc.Content, "错误处理") || strings.Contains(doc.Content, "error handling") { + suggestions = append(suggestions, "建议添加完善的错误处理逻辑(参考知识库文档)") + break + } + } + } + + // 检查日志记录 + if !strings.Contains(goCode, "proxywasm.Log") { + suggestions = append(suggestions, "建议添加适当的日志记录以便调试") + } + + return suggestions +} + +func generateBasicValidationSuggestions(goCode string) string { + return "- 确保所有 API 调用都有错误处理\n" + + "- 添加必要的日志记录\n" + + "- 遵循 Higress WASM 插件开发规范\n" +} + +func performRuleBasedConversion(luaCode string, pluginName string) string { + // 简化实现:基于规则的转换 + return fmt.Sprintf(`package main + +import ( + "github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types" + "github.com/higress-group/wasm-go/pkg/wrapper" +) + +func main() { + wrapper.SetCtx( + "%s", + wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders), + ) +} + +func onHttpRequestHeaders(ctx wrapper.HttpContext, config PluginConfig, log wrapper.Log) types.Action { + // TODO: 实现转换逻辑 + // 原始 Lua 代码: + // %s + + return types.ActionContinue +} + +type PluginConfig struct { + // TODO: 添加配置字段 +} +`, pluginName, luaCode) +} diff --git a/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/integration/mcptools/tool_chain.go b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/integration/mcptools/tool_chain.go new file mode 100644 index 000000000..324a26f12 --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/integration/mcptools/tool_chain.go @@ -0,0 +1,304 @@ +//go:build higress_integration +// +build higress_integration + +package mcptools + +import ( + "encoding/json" + "fmt" + "strings" + + "nginx-migration-mcp/tools" + + "github.com/alibaba/higress/plugins/golang-filter/mcp-session/common" +) + +// RegisterToolChainTools 注册工具链相关的工具 +func RegisterToolChainTools(server *common.MCPServer, ctx *MigrationContext) { + RegisterSimpleTool( + server, + "generate_conversion_hints", + "基于 Lua 分析结果生成代码转换模板", + map[string]interface{}{ + "type": "object", + "properties": map[string]interface{}{ + "analysis_result": map[string]interface{}{ + "type": "string", + "description": "analyze_lua_plugin 返回的 JSON 格式分析结果", + }, + "plugin_name": map[string]interface{}{ + "type": "string", + "description": "目标插件名称(小写字母和连字符)", + }, + }, + "required": []string{"analysis_result", "plugin_name"}, + }, + func(args map[string]interface{}) (string, error) { + return generateConversionHints(args, ctx) + }, + ) + + RegisterSimpleTool( + server, + "validate_wasm_code", + "验证生成的 Go WASM 插件代码,检查语法、API 使用和配置结构", + map[string]interface{}{ + "type": "object", + "properties": map[string]interface{}{ + "go_code": map[string]interface{}{ + "type": "string", + "description": "生成的 Go WASM 插件代码", + }, + "plugin_name": map[string]interface{}{ + "type": "string", + "description": "插件名称", + }, + }, + "required": []string{"go_code", "plugin_name"}, + }, + func(args map[string]interface{}) (string, error) { + return validateWasmCode(args, ctx) + }, + ) + + RegisterSimpleTool( + server, + "generate_deployment_config", + "为验证通过的 WASM 插件生成完整的部署配置包", + map[string]interface{}{ + "type": "object", + "properties": map[string]interface{}{ + "plugin_name": map[string]interface{}{ + "type": "string", + "description": "插件名称", + }, + "go_code": map[string]interface{}{ + "type": "string", + "description": "验证通过的 Go 代码", + }, + "config_schema": map[string]interface{}{ + "type": "string", + "description": "配置 JSON Schema(可选)", + }, + "namespace": map[string]interface{}{ + "type": "string", + "description": "部署命名空间", + "default": "higress-system", + }, + }, + "required": []string{"plugin_name", "go_code"}, + }, + func(args map[string]interface{}) (string, error) { + return generateDeploymentConfig(args, ctx) + }, + ) +} + +func generateConversionHints(args map[string]interface{}, ctx *MigrationContext) (string, error) { + analysisResultStr, ok := args["analysis_result"].(string) + if !ok { + return "", fmt.Errorf("missing or invalid analysis_result parameter") + } + + pluginName, ok := args["plugin_name"].(string) + if !ok { + return "", fmt.Errorf("missing or invalid plugin_name parameter") + } + + // 解析分析结果 + var analysis tools.AnalysisResultForAI + if err := json.Unmarshal([]byte(analysisResultStr), &analysis); err != nil { + return "", fmt.Errorf("failed to parse analysis_result: %w", err) + } + + // 生成转换提示 + hints := tools.GenerateConversionHints(analysis, pluginName) + + // === RAG 增强(如果启用)=== + var ragInfo map[string]interface{} + if ctx.RAGManager != nil && ctx.RAGManager.IsEnabled() { + // 构建智能查询语句 + queryBuilder := []string{} + if len(analysis.APICalls) > 0 { + queryBuilder = append(queryBuilder, "Nginx Lua API 转换到 Higress WASM") + + hasHeaderOps := analysis.Features["header_manipulation"] || analysis.Features["request_headers"] || analysis.Features["response_headers"] + hasBodyOps := analysis.Features["request_body"] || analysis.Features["response_body"] + hasResponseControl := analysis.Features["response_control"] + + if hasHeaderOps { + queryBuilder = append(queryBuilder, "请求头和响应头处理") + } + if hasBodyOps { + queryBuilder = append(queryBuilder, "请求体和响应体处理") + } + if hasResponseControl { + queryBuilder = append(queryBuilder, "响应控制和状态码设置") + } + + if len(analysis.APICalls) > 0 && len(analysis.APICalls) <= 5 { + queryBuilder = append(queryBuilder, fmt.Sprintf("涉及 API: %s", strings.Join(analysis.APICalls, ", "))) + } + } else { + queryBuilder = append(queryBuilder, "Higress WASM 插件开发 基础示例 Go SDK 使用") + } + + if analysis.Complexity == "high" { + queryBuilder = append(queryBuilder, "复杂插件实现 高级功能") + } + + queryString := strings.Join(queryBuilder, " ") + + ragContext, err := ctx.RAGManager.QueryForTool( + "generate_conversion_hints", + queryString, + "lua_migration", + ) + if err == nil && ragContext.Enabled && len(ragContext.Documents) > 0 { + ragInfo = map[string]interface{}{ + "enabled": true, + "documents": len(ragContext.Documents), + "context": ragContext.FormatContextForAI(), + } + } + } + + // 组合结果 + result := map[string]interface{}{ + "code_template": hints.CodeTemplate, + "warnings": hints.Warnings, + "rag": ragInfo, + } + + // 返回 JSON 结果,由 LLM 解释和使用 + resultJSON, _ := json.MarshalIndent(result, "", " ") + return string(resultJSON), nil +} + +func validateWasmCode(args map[string]interface{}, ctx *MigrationContext) (string, error) { + goCode, ok := args["go_code"].(string) + if !ok { + return "", fmt.Errorf("missing or invalid go_code parameter") + } + + pluginName, ok := args["plugin_name"].(string) + if !ok { + return "", fmt.Errorf("missing or invalid plugin_name parameter") + } + + // 执行验证(AI 驱动) + report := tools.ValidateWasmCode(goCode, pluginName) + + // 格式化输出,包含 AI 分析提示和基础信息 + var result strings.Builder + + result.WriteString(fmt.Sprintf("## 代码验证报告\n\n")) + result.WriteString(fmt.Sprintf("代码存在 %d 个必须修复的问题,%d 个建议修复的问题,%d 个可选优化项,%d 个最佳实践建议。请优先解决必须修复的问题。\n\n", 0, 0, 0, 0)) + + result.WriteString(fmt.Sprintf("### 发现的回调函数 (%d 个)\n", len(report.FoundCallbacks))) + if len(report.FoundCallbacks) > 0 { + for _, cb := range report.FoundCallbacks { + result.WriteString(fmt.Sprintf("- %s\n", cb)) + } + } else { + result.WriteString("无\n") + } + result.WriteString("\n") + + result.WriteString("### 配置结构\n") + if report.HasConfig { + result.WriteString(" 已定义配置结构体\n\n") + } else { + result.WriteString(" 未定义配置结构体\n\n") + } + + result.WriteString("### 问题分类\n\n") + + result.WriteString("#### 必须修复 (0 个)\n") + result.WriteString("无\n\n") + + result.WriteString("#### 建议修复 (0 个)\n") + result.WriteString("无\n\n") + + result.WriteString("#### 可选优化 (0 个)\n") + result.WriteString("无\n\n") + + result.WriteString("#### 最佳实践 (0 个)\n") + result.WriteString("无\n\n") + + // 添加 AI 分析提示 + result.WriteString("---\n\n") + result.WriteString(report.Summary) + result.WriteString("\n\n") + + // === RAG 增强:查询最佳实践 === + if ctx.RAGManager != nil && ctx.RAGManager.IsEnabled() { + // 构建智能查询语句 + queryBuilder := []string{"Higress WASM 插件"} + + // 根据回调函数类型添加特定查询 + for _, callback := range report.FoundCallbacks { + if strings.Contains(callback, "RequestHeaders") { + queryBuilder = append(queryBuilder, "请求头处理") + } + if strings.Contains(callback, "RequestBody") { + queryBuilder = append(queryBuilder, "请求体处理") + } + if strings.Contains(callback, "ResponseHeaders") { + queryBuilder = append(queryBuilder, "响应头处理") + } + } + + queryBuilder = append(queryBuilder, "性能优化 最佳实践 错误处理") + queryString := strings.Join(queryBuilder, " ") + + ragContext, err := ctx.RAGManager.QueryForTool( + "validate_wasm_code", + queryString, + "best_practice", + ) + if err == nil && ragContext.Enabled && len(ragContext.Documents) > 0 { + result.WriteString("\n\n### 📚 最佳实践建议(来自知识库)\n\n") + result.WriteString(ragContext.FormatContextForAI()) + } + } + + // 添加 JSON 格式的结构化数据(供后续处理) + reportJSON, _ := json.MarshalIndent(report, "", " ") + result.WriteString("\n") + result.WriteString(string(reportJSON)) + + return result.String(), nil +} + +func generateDeploymentConfig(args map[string]interface{}, ctx *MigrationContext) (string, error) { + pluginName, ok := args["plugin_name"].(string) + if !ok { + return "", fmt.Errorf("missing or invalid plugin_name parameter") + } + + _, ok = args["go_code"].(string) + if !ok { + return "", fmt.Errorf("missing or invalid go_code parameter") + } + + namespace := "higress-system" + if ns, ok := args["namespace"].(string); ok && ns != "" { + namespace = ns + } + + // configSchema is optional, we don't use it for now but don't return error + _ = args["config_schema"] + + // 返回提示信息,由 LLM 生成具体配置文件 + result := fmt.Sprintf(`为插件 %s 生成以下部署配置: +1. WasmPlugin YAML (namespace: %s) +2. Makefile (TinyGo 构建) +3. Dockerfile +4. README.md +5. 测试脚本 + +参考文档: https://higress.cn/docs/latest/user/wasm-go/`, pluginName, namespace) + + return result, nil +} diff --git a/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/integration/server.go b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/integration/server.go new file mode 100644 index 000000000..e844c9ccd --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/integration/server.go @@ -0,0 +1,126 @@ +//go:build higress_integration +// +build higress_integration + +package nginx_migration + +import ( + "errors" + + "nginx-migration-mcp/integration/mcptools" + "nginx-migration-mcp/internal/rag" + + "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("nginx-migration", &NginxMigrationConfig{}) +} + +// NginxMigrationConfig holds configuration for the Nginx Migration MCP Server +type NginxMigrationConfig struct { + gatewayName string + gatewayNamespace string + defaultNamespace string + defaultHostname string + description string + ragConfigPath string // RAG 配置文件路径 +} + +// ParseConfig parses the configuration map for the Nginx Migration server +func (c *NginxMigrationConfig) ParseConfig(config map[string]interface{}) error { + // Optional configurations with defaults + if gatewayName, ok := config["gatewayName"].(string); ok { + c.gatewayName = gatewayName + } else { + c.gatewayName = "higress-gateway" + } + + if gatewayNamespace, ok := config["gatewayNamespace"].(string); ok { + c.gatewayNamespace = gatewayNamespace + } else { + c.gatewayNamespace = "higress-system" + } + + if defaultNamespace, ok := config["defaultNamespace"].(string); ok { + c.defaultNamespace = defaultNamespace + } else { + c.defaultNamespace = "default" + } + + if defaultHostname, ok := config["defaultHostname"].(string); ok { + c.defaultHostname = defaultHostname + } else { + c.defaultHostname = "example.com" + } + + if desc, ok := config["description"].(string); ok { + c.description = desc + } else { + c.description = "Nginx Migration MCP Server - Convert Nginx configs and Lua plugins to Higress" + } + + // RAG 配置路径(可选) + if ragPath, ok := config["ragConfigPath"].(string); ok { + c.ragConfigPath = ragPath + } else { + c.ragConfigPath = "config/rag.json" // 默认路径 + } + + api.LogDebugf("NginxMigrationConfig ParseConfig: gatewayName=%s, gatewayNamespace=%s, defaultNamespace=%s, ragConfig=%s", + c.gatewayName, c.gatewayNamespace, c.defaultNamespace, c.ragConfigPath) + + return nil +} + +// NewServer creates a new MCP server instance for Nginx Migration +func (c *NginxMigrationConfig) NewServer(serverName string) (*common.MCPServer, error) { + if serverName == "" { + return nil, errors.New("server name cannot be empty") + } + + mcpServer := common.NewMCPServer( + serverName, + Version, + common.WithInstructions("Nginx Migration MCP Server: Analyze and convert Nginx configurations and Lua plugins to Higress"), + ) + + // Create migration context with configuration + migrationCtx := &mcptools.MigrationContext{ + GatewayName: c.gatewayName, + GatewayNamespace: c.gatewayNamespace, + DefaultNamespace: c.defaultNamespace, + DefaultHostname: c.defaultHostname, + } + + // 初始化 RAG Manager(如果配置了) + if c.ragConfigPath != "" { + api.LogInfof("Loading RAG config from: %s", c.ragConfigPath) + ragConfig, err := rag.LoadRAGConfig(c.ragConfigPath) + if err != nil { + api.LogWarnf("Failed to load RAG config: %v, RAG will be disabled", err) + // 不返回错误,继续使用无 RAG 的模式 + ragConfig = &rag.RAGConfig{Enabled: false} + } + + // 创建 RAG Manager + migrationCtx.RAGManager = rag.NewRAGManager(ragConfig) + + if migrationCtx.RAGManager.IsEnabled() { + api.LogInfof("✅ RAG enabled for Nginx Migration MCP Server") + } else { + api.LogInfof("📖 RAG disabled, using rule-based approach") + } + } + + // Register all migration tools + mcptools.RegisterNginxConfigTools(mcpServer, migrationCtx) + mcptools.RegisterLuaPluginTools(mcpServer, migrationCtx) + mcptools.RegisterToolChainTools(mcpServer, migrationCtx) + + api.LogInfof("Nginx Migration MCP Server initialized: %s (tools registered)", serverName) + + return mcpServer, nil +} diff --git a/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/internal/rag/client.go b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/internal/rag/client.go new file mode 100644 index 000000000..bd3a6aaa1 --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/internal/rag/client.go @@ -0,0 +1,329 @@ +// Package rag 提供基于阿里云官方 SDK 的 RAG 客户端实现 +package rag + +import ( + "fmt" + "log" + "sync" + "time" + + bailian "github.com/alibabacloud-go/bailian-20231229/v2/client" + openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client" + util "github.com/alibabacloud-go/tea-utils/v2/service" + "github.com/alibabacloud-go/tea/tea" +) + +// RAGQuery RAG 查询请求 +type RAGQuery struct { + Query string `json:"query"` // 查询文本 + Scenario string `json:"scenario"` // 场景标识 + TopK int `json:"top_k"` // 返回文档数量 + ContextMode string `json:"context_mode"` // 上下文模式 + Filters map[string]interface{} `json:"filters"` // 过滤条件 +} + +// RAGResponse RAG 查询响应 +type RAGResponse struct { + Documents []RAGDocument `json:"documents"` // 检索到的文档 + Latency int64 `json:"latency"` // 查询延迟(毫秒) +} + +// RAGDocument 表示一个检索到的文档 +type RAGDocument struct { + Title string `json:"title"` // 文档标题 + Content string `json:"content"` // 文档内容 + Source string `json:"source"` // 来源路径 + URL string `json:"url"` // 在线链接 + Score float64 `json:"score"` // 相关度分数 + Highlights []string `json:"highlights"` // 高亮片段 +} + +// RAGClient 使用阿里云官方 SDK 的 RAG 客户端 +type RAGClient struct { + config *RAGConfig + client *bailian.Client + cache *QueryCache +} + +// NewRAGClient 创建基于 SDK 的 RAG 客户端 +func NewRAGClient(config *RAGConfig) (*RAGClient, error) { + // 创建 SDK 配置 + sdkConfig := &openapi.Config{ + AccessKeyId: tea.String(config.AccessKeyID), + AccessKeySecret: tea.String(config.AccessKeySecret), + } + + // 设置端点(默认为北京区域) + if config.Endpoint != "" { + sdkConfig.Endpoint = tea.String(config.Endpoint) + } else { + sdkConfig.Endpoint = tea.String("bailian.cn-beijing.aliyuncs.com") + } + + // 创建客户端 + client, err := bailian.NewClient(sdkConfig) + if err != nil { + return nil, fmt.Errorf("failed to create Bailian SDK client: %w", err) + } + + c := &RAGClient{ + config: config, + client: client, + } + + // 初始化缓存 + if config.EnableCache { + c.cache = NewQueryCache(config.CacheMaxSize, time.Duration(config.CacheTTL)*time.Second) + } + + return c, nil +} + +// SearchWithCache 查询知识库(带缓存) +func (c *RAGClient) SearchWithCache(query *RAGQuery) (*RAGResponse, error) { + // 检查缓存 + if c.cache != nil { + cacheKey := c.buildCacheKey(query) + if cached := c.cache.Get(cacheKey); cached != nil { + if c.config.Debug { + log.Printf("🎯 RAG cache hit: %s", query.Query) + } + return cached, nil + } + } + + // 执行查询 + startTime := time.Now() + resp, err := c.search(query) + if err != nil { + return nil, err + } + + // 记录延迟 + resp.Latency = time.Since(startTime).Milliseconds() + + // 缓存结果 + if c.cache != nil { + cacheKey := c.buildCacheKey(query) + c.cache.Set(cacheKey, resp) + } + + if c.config.Debug { + log.Printf("✅ RAG query completed: %s (latency: %dms, docs: %d)", + query.Query, resp.Latency, len(resp.Documents)) + } + + return resp, nil +} + +// search 执行实际的查询(带重试) +func (c *RAGClient) search(query *RAGQuery) (*RAGResponse, error) { + var lastErr error + + for attempt := 0; attempt <= c.config.MaxRetries; attempt++ { + if attempt > 0 { + // 重试前等待 + time.Sleep(time.Duration(c.config.RetryDelay) * time.Second) + log.Printf("🔄 Retrying RAG query (attempt %d/%d)", attempt, c.config.MaxRetries) + } + + resp, err := c.doSearchSDK(query) + if err == nil { + return resp, nil + } + + lastErr = err + } + + return nil, fmt.Errorf("RAG query failed after %d retries: %w", c.config.MaxRetries, lastErr) +} + +// doSearchSDK 执行单次查询(使用 SDK) +func (c *RAGClient) doSearchSDK(query *RAGQuery) (*RAGResponse, error) { + // 构建检索请求 + request := &bailian.RetrieveRequest{ + IndexId: tea.String(c.config.KnowledgeBaseID), + Query: tea.String(query.Query), + } + + // 设置可选参数 + if query.TopK > 0 { + request.DenseSimilarityTopK = tea.Int32(int32(query.TopK)) + } else { + request.DenseSimilarityTopK = tea.Int32(int32(c.config.DefaultTopK)) + } + + // 启用重排序 + request.EnableReranking = tea.Bool(true) + + // 准备请求头和运行时选项 + headers := make(map[string]*string) + runtime := &util.RuntimeOptions{} + + // 调用 SDK 检索接口 + response, err := c.client.RetrieveWithOptions( + tea.String(c.config.WorkspaceID), + request, + headers, + runtime, + ) + + if err != nil { + return nil, fmt.Errorf("SDK retrieve failed: %w", err) + } + + // 检查响应 + if response == nil || response.Body == nil { + return nil, fmt.Errorf("empty response from SDK") + } + + if !tea.BoolValue(response.Body.Success) { + return nil, fmt.Errorf("SDK returned Success=false, Code=%s, Message=%s", + tea.StringValue(response.Body.Code), + tea.StringValue(response.Body.Message)) + } + + // 转换为 RAGResponse + ragResp := &RAGResponse{ + Documents: make([]RAGDocument, 0), + } + + if response.Body.Data != nil && response.Body.Data.Nodes != nil { + for _, node := range response.Body.Data.Nodes { + if node == nil { + continue + } + + // 过滤低相关度文档 + score := tea.Float64Value(node.Score) + if score < c.config.SimilarityThreshold { + continue + } + + // 从 Metadata 中提取信息 + title := "" + source := "" + url := "" + + if node.Metadata != nil { + // Metadata 是 interface{} 类型,需要先转换为 map + if meta, ok := node.Metadata.(map[string]interface{}); ok { + if t, ok := meta["title"].(string); ok { + title = t + } + if s, ok := meta["doc_name"].(string); ok { + source = s + } + if u, ok := meta["file_path"].(string); ok { + url = u + } + } + } + + ragResp.Documents = append(ragResp.Documents, RAGDocument{ + Title: title, + Content: tea.StringValue(node.Text), + Source: source, + URL: url, + Score: score, + Highlights: []string{}, // SDK 不返回 highlights + }) + } + } + + return ragResp, nil +} + +// buildCacheKey 构建缓存键 +func (c *RAGClient) buildCacheKey(query *RAGQuery) string { + return fmt.Sprintf("%s:%s:top%d:%s", query.Scenario, query.Query, query.TopK, query.ContextMode) +} + +// QueryCache 查询缓存 +type QueryCache struct { + entries map[string]*CacheEntry + mu sync.RWMutex + maxSize int + ttl time.Duration +} + +// CacheEntry 缓存条目 +type CacheEntry struct { + Response *RAGResponse + ExpiresAt time.Time +} + +// NewQueryCache 创建查询缓存 +func NewQueryCache(maxSize int, ttl time.Duration) *QueryCache { + cache := &QueryCache{ + entries: make(map[string]*CacheEntry), + maxSize: maxSize, + ttl: ttl, + } + + // 启动清理协程 + go cache.cleanupLoop() + + return cache +} + +// Get 获取缓存 +func (c *QueryCache) Get(key string) *RAGResponse { + c.mu.RLock() + defer c.mu.RUnlock() + + entry, ok := c.entries[key] + if !ok { + return nil + } + + // 检查是否过期 + if time.Now().After(entry.ExpiresAt) { + return nil + } + + return entry.Response +} + +// Set 设置缓存 +func (c *QueryCache) Set(key string, resp *RAGResponse) { + c.mu.Lock() + defer c.mu.Unlock() + + // 检查缓存大小 + if len(c.entries) >= c.maxSize { + // 简单的 LRU:删除第一个条目 + for k := range c.entries { + delete(c.entries, k) + break + } + } + + c.entries[key] = &CacheEntry{ + Response: resp, + ExpiresAt: time.Now().Add(c.ttl), + } +} + +// cleanupLoop 清理过期缓存 +func (c *QueryCache) cleanupLoop() { + ticker := time.NewTicker(5 * time.Minute) + defer ticker.Stop() + + for range ticker.C { + c.cleanup() + } +} + +// cleanup 执行清理 +func (c *QueryCache) cleanup() { + c.mu.Lock() + defer c.mu.Unlock() + + now := time.Now() + for key, entry := range c.entries { + if now.After(entry.ExpiresAt) { + delete(c.entries, key) + } + } +} diff --git a/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/internal/rag/config.go b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/internal/rag/config.go new file mode 100644 index 000000000..1cc5c44e3 --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/internal/rag/config.go @@ -0,0 +1,188 @@ +// Package rag 提供 RAG(检索增强生成)配置管理 +package rag + +import ( + "encoding/json" + "fmt" + "os" + "strings" +) + +// RAGConfig RAG 完整配置 +type RAGConfig struct { + Enabled bool `json:"enabled" yaml:"enabled"` // RAG 功能总开关 + + // API 配置 + Provider string `json:"provider" yaml:"provider"` // 服务提供商: "bailian" + Endpoint string `json:"endpoint" yaml:"endpoint"` // API 端点(如 bailian.cn-beijing.aliyuncs.com) + WorkspaceID string `json:"workspace_id" yaml:"workspace_id"` // 业务空间 ID(百炼必需) + AccessKeyID string `json:"access_key_id" yaml:"access_key_id"` // 阿里云 AccessKey ID + AccessKeySecret string `json:"access_key_secret" yaml:"access_key_secret"` // 阿里云 AccessKey Secret + KnowledgeBaseID string `json:"knowledge_base_id" yaml:"knowledge_base_id"` // 知识库 ID(IndexId) + + // 上下文配置 + ContextMode string `json:"context_mode" yaml:"context_mode"` // full | summary | highlights + MaxContextLength int `json:"max_context_length" yaml:"max_context_length"` // 最大上下文长度(字符数) + + // 检索配置 + DefaultTopK int `json:"default_top_k" yaml:"default_top_k"` // 默认返回文档数量 + SimilarityThreshold float64 `json:"similarity_threshold" yaml:"similarity_threshold"` // 相似度阈值 + + // 缓存配置 + EnableCache bool `json:"enable_cache" yaml:"enable_cache"` // 是否启用缓存 + CacheTTL int `json:"cache_ttl" yaml:"cache_ttl"` // 缓存过期时间(秒) + CacheMaxSize int `json:"cache_max_size" yaml:"cache_max_size"` // 最大缓存条目数 + + // 性能配置 + Timeout int `json:"timeout" yaml:"timeout"` // 请求超时时间(秒) + MaxRetries int `json:"max_retries" yaml:"max_retries"` // 最大重试次数 + RetryDelay int `json:"retry_delay" yaml:"retry_delay"` // 重试间隔(秒) + + // 降级策略 + FallbackOnError bool `json:"fallback_on_error" yaml:"fallback_on_error"` // RAG 失败时是否降级 + + // 工具级别配置(核心功能) + Tools map[string]*ToolConfig `json:"tools" yaml:"tools"` + + // 调试模式 + Debug bool `json:"debug" yaml:"debug"` // 是否启用调试日志 + LogQueries bool `json:"log_queries" yaml:"log_queries"` // 是否记录所有查询 +} + +// ToolConfig 工具级别的 RAG 配置 +type ToolConfig struct { + UseRAG bool `json:"use_rag" yaml:"use_rag"` // 是否使用 RAG + ContextMode string `json:"context_mode" yaml:"context_mode"` // 上下文模式(覆盖全局配置) + TopK int `json:"top_k" yaml:"top_k"` // 返回文档数量(覆盖全局配置) +} + +// LoadRAGConfig 从配置文件加载 RAG 配置 +// 注意:需要安装 YAML 库支持 +// 运行:go get gopkg.in/yaml.v3 +// +// 临时实现:使用 JSON 格式配置文件 +func LoadRAGConfig(configPath string) (*RAGConfig, error) { + // 读取配置文件 + data, err := os.ReadFile(configPath) + if err != nil { + return nil, fmt.Errorf("failed to read config file: %w", err) + } + + // 简单的 YAML 到 JSON 转换(仅支持基本格式) + // 在生产环境中应使用真正的 YAML 解析器 + jsonData := simpleYAMLToJSON(string(data)) + + // 解析 JSON + var wrapper struct { + RAG *RAGConfig `json:"rag"` + } + + if err := json.Unmarshal([]byte(jsonData), &wrapper); err != nil { + return nil, fmt.Errorf("failed to parse config: %w", err) + } + + if wrapper.RAG == nil { + return nil, fmt.Errorf("missing 'rag' section in config") + } + + config := wrapper.RAG + + // 展开环境变量 + config.AccessKeyID = expandEnvVar(config.AccessKeyID) + config.AccessKeySecret = expandEnvVar(config.AccessKeySecret) + config.KnowledgeBaseID = expandEnvVar(config.KnowledgeBaseID) + config.WorkspaceID = expandEnvVar(config.WorkspaceID) + + // 设置默认值 + setDefaults(config) + + return config, nil +} + +// simpleYAMLToJSON 简单的 YAML 到 JSON 转换 +// 注意:这是一个临时实现,仅支持基本的 YAML 格式 +// 生产环境请使用 gopkg.in/yaml.v3 +func simpleYAMLToJSON(yamlContent string) string { + trimmed := strings.TrimSpace(yamlContent) + + // 如果内容看起来像 JSON,直接返回 + if strings.HasPrefix(trimmed, "{") { + return yamlContent + } + + // 否则返回默认禁用配置 + return `{"rag": {"enabled": false}}` +} + +// expandEnvVar 展开环境变量 ${VAR_NAME} +func expandEnvVar(value string) string { + if strings.HasPrefix(value, "${") && strings.HasSuffix(value, "}") { + varName := value[2 : len(value)-1] + return os.Getenv(varName) + } + return value +} + +// setDefaults 设置默认值 +func setDefaults(config *RAGConfig) { + if config.ContextMode == "" { + config.ContextMode = "full" + } + if config.MaxContextLength == 0 { + config.MaxContextLength = 4000 + } + if config.DefaultTopK == 0 { + config.DefaultTopK = 3 + } + if config.SimilarityThreshold == 0 { + config.SimilarityThreshold = 0.7 + } + if config.CacheTTL == 0 { + config.CacheTTL = 3600 + } + if config.CacheMaxSize == 0 { + config.CacheMaxSize = 1000 + } + if config.Timeout == 0 { + config.Timeout = 10 + } + if config.MaxRetries == 0 { + config.MaxRetries = 3 + } + if config.RetryDelay == 0 { + config.RetryDelay = 1 + } + + // 为每个工具配置设置默认值 + for _, toolConfig := range config.Tools { + if toolConfig.ContextMode == "" { + toolConfig.ContextMode = config.ContextMode + } + if toolConfig.TopK == 0 { + toolConfig.TopK = config.DefaultTopK + } + } +} + +// GetToolConfig 获取指定工具的配置 +func (c *RAGConfig) GetToolConfig(toolName string) *ToolConfig { + if toolConfig, ok := c.Tools[toolName]; ok { + return toolConfig + } + return nil +} + +// IsToolRAGEnabled 检查指定工具是否启用 RAG +func (c *RAGConfig) IsToolRAGEnabled(toolName string) bool { + if !c.Enabled { + return false + } + + toolConfig := c.GetToolConfig(toolName) + if toolConfig == nil { + // 没有工具级配置,使用全局配置 + return c.Enabled + } + + return toolConfig.UseRAG +} diff --git a/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/internal/rag/manager.go b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/internal/rag/manager.go new file mode 100644 index 000000000..437a68675 --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/internal/rag/manager.go @@ -0,0 +1,302 @@ +// Package rag 提供 RAG(检索增强生成)功能 +// 支持可选的知识库集成,通过配置开关控制 +package rag + +import ( + "fmt" + "log" + "strings" +) + +// RAGManager 管理 RAG 功能的开关和查询 +type RAGManager struct { + enabled bool // RAG 功能是否启用 + client *RAGClient // RAG 客户端(仅在 enabled=true 时有效) + config *RAGConfig // 配置 +} + +// NewRAGManager 创建 RAG 管理器 +// 如果配置中 enabled=false,则返回禁用状态的管理器 +func NewRAGManager(config *RAGConfig) *RAGManager { + if config == nil || !config.Enabled { + log.Println("📖 RAG: Disabled (using rule-based generation)") + return &RAGManager{ + enabled: false, + config: config, + } + } + + // 验证必要配置 + if config.KnowledgeBaseID == "" || config.WorkspaceID == "" { + log.Println("⚠️ RAG: Missing workspace ID or knowledge base ID, disabling RAG") + return &RAGManager{ + enabled: false, + config: config, + } + } + + // 检查 SDK 认证凭证 + if config.AccessKeyID == "" || config.AccessKeySecret == "" { + log.Println("⚠️ RAG: Missing AccessKey credentials, disabling RAG") + return &RAGManager{ + enabled: false, + config: config, + } + } + + // 初始化 RAG 客户端(使用 SDK) + log.Println("🔧 RAG: Using Alibaba Cloud SDK authentication") + client, err := NewRAGClient(config) + if err != nil { + log.Printf("❌ RAG: Failed to initialize SDK client: %v, disabling RAG\n", err) + return &RAGManager{ + enabled: false, + config: config, + } + } + + log.Printf("✅ RAG: Enabled (Provider: %s, KB: %s)\n", config.Provider, config.KnowledgeBaseID) + + return &RAGManager{ + enabled: true, + client: client, + config: config, + } +} + +// IsEnabled 返回 RAG 是否启用 +func (m *RAGManager) IsEnabled() bool { + return m.enabled +} + +// QueryWithContext 查询知识库并返回上下文 +// 如果 RAG 未启用,返回空上下文(不报错) +func (m *RAGManager) QueryWithContext(query string, scenario string, opts ...QueryOption) (*RAGContext, error) { + // RAG 未启用,返回空上下文 + if !m.enabled { + return &RAGContext{ + Enabled: false, + Message: "RAG is disabled, using rule-based generation", + }, nil + } + + // 构建查询 + ragQuery := &RAGQuery{ + Query: query, + Scenario: scenario, + TopK: m.config.DefaultTopK, + } + + // 应用可选参数 + for _, opt := range opts { + opt(ragQuery) + } + + // 查询知识库 + resp, err := m.client.SearchWithCache(ragQuery) + if err != nil { + // 如果配置了降级策略,返回空上下文而不是报错 + if m.config.FallbackOnError { + log.Printf("⚠️ RAG query failed, falling back to rules: %v\n", err) + return &RAGContext{ + Enabled: false, + Message: fmt.Sprintf("RAG query failed, using fallback: %v", err), + }, nil + } + return nil, fmt.Errorf("RAG query failed: %w", err) + } + + // 构建上下文 + return m.buildContext(resp), nil +} + +// QueryForTool 为特定工具查询(支持工具级配置覆盖) +// 这是工具级别配置的核心实现 +func (m *RAGManager) QueryForTool(toolName string, query string, scenario string) (*RAGContext, error) { + // 全局 RAG 未启用 + if !m.enabled { + return &RAGContext{ + Enabled: false, + Message: "RAG is disabled globally", + }, nil + } + + // 检查工具级配置 + if toolConfig, ok := m.config.Tools[toolName]; ok { + // 工具有专门的配置 + if !toolConfig.UseRAG { + // 工具明确不使用 RAG + return &RAGContext{ + Enabled: false, + Message: fmt.Sprintf("RAG is disabled for tool: %s", toolName), + }, nil + } + + // 使用工具级配置覆盖全局配置 + log.Printf("🔧 Using tool-specific RAG config for: %s (context_mode=%s, top_k=%d)", + toolName, toolConfig.ContextMode, toolConfig.TopK) + + return m.QueryWithContext(query, scenario, + WithTopK(toolConfig.TopK), + WithContextMode(toolConfig.ContextMode), + ) + } + + // 没有工具级配置,使用默认全局配置 + log.Printf("🔧 Using global RAG config for: %s", toolName) + return m.QueryWithContext(query, scenario) +} + +// buildContext 构建上下文 +func (m *RAGManager) buildContext(resp *RAGResponse) *RAGContext { + ctx := &RAGContext{ + Enabled: true, + Documents: make([]ContextDocument, 0, len(resp.Documents)), + } + + for _, doc := range resp.Documents { + ctxDoc := ContextDocument{ + Title: doc.Title, + Source: doc.Source, + URL: doc.URL, + Score: doc.Score, + Highlights: doc.Highlights, + } + + // 根据 context_mode 决定返回的内容 + switch m.config.ContextMode { + case "full": + ctxDoc.Content = doc.Content + case "summary": + ctxDoc.Content = m.summarize(doc.Content) + case "highlights": + if len(doc.Highlights) > 0 { + ctxDoc.Content = strings.Join(doc.Highlights, "\n\n") + } else { + ctxDoc.Content = m.summarize(doc.Content) + } + default: + ctxDoc.Content = doc.Content + } + + // 控制长度 + if len(ctxDoc.Content) > m.config.MaxContextLength { + ctxDoc.Content = ctxDoc.Content[:m.config.MaxContextLength] + "\n\n[内容已截断...]" + } + + ctx.Documents = append(ctx.Documents, ctxDoc) + } + + ctx.Message = fmt.Sprintf("Retrieved %d relevant documents from knowledge base (latency: %dms)", + len(ctx.Documents), resp.Latency) + + return ctx +} + +// summarize 简单的内容摘要(截取前N个字符) +func (m *RAGManager) summarize(content string, maxLen ...int) string { + length := 500 // 默认500字符 + if len(maxLen) > 0 { + length = maxLen[0] + } + + if len(content) <= length { + return content + } + + // 尝试在句号或换行处截断 + truncated := content[:length] + if idx := strings.LastIndexAny(truncated, "。\n."); idx > length/2 { + return content[:idx+1] + } + + return truncated + "..." +} + +// FormatContextForAI 格式化上下文,供 AI 使用 +// 返回 Markdown 格式的文档上下文 +func (ctx *RAGContext) FormatContextForAI() string { + if !ctx.Enabled || len(ctx.Documents) == 0 { + return fmt.Sprintf("> ℹ️ %s\n", ctx.Message) + } + + var result strings.Builder + + result.WriteString("## 📚 知识库参考文档\n\n") + result.WriteString(fmt.Sprintf("> %s\n\n", ctx.Message)) + + for i, doc := range ctx.Documents { + result.WriteString(fmt.Sprintf("### 参考文档 %d: %s\n\n", i+1, doc.Title)) + + // 元信息 + result.WriteString(fmt.Sprintf("**来源**: %s \n", doc.Source)) + if doc.URL != "" { + result.WriteString(fmt.Sprintf("**链接**: %s \n", doc.URL)) + } + result.WriteString(fmt.Sprintf("**相关度**: %.2f \n\n", doc.Score)) + + // 文档内容(重点) + result.WriteString("**相关内容**:\n\n") + result.WriteString("```\n") + result.WriteString(doc.Content) + result.WriteString("\n```\n\n") + + // 高亮片段 + if len(doc.Highlights) > 0 { + result.WriteString("**关键片段**:\n\n") + for _, h := range doc.Highlights { + result.WriteString(fmt.Sprintf("- %s\n", h)) + } + result.WriteString("\n") + } + + result.WriteString("---\n\n") + } + + return result.String() +} + +// ==================== 类型定义 ==================== + +// RAGContext 表示 RAG 查询返回的上下文 +type RAGContext struct { + Enabled bool `json:"enabled"` // RAG 是否启用 + Documents []ContextDocument `json:"documents"` // 检索到的文档 + Message string `json:"message"` // 提示信息 +} + +// ContextDocument 表示上下文中的一个文档 +type ContextDocument struct { + Title string `json:"title"` // 文档标题 + Content string `json:"content"` // 文档内容(根据 context_mode 调整) + Source string `json:"source"` // 来源路径 + URL string `json:"url"` // 在线链接 + Score float64 `json:"score"` // 相关度分数 + Highlights []string `json:"highlights"` // 高亮片段 +} + +// ==================== 查询选项 ==================== + +// QueryOption 查询选项函数 +type QueryOption func(*RAGQuery) + +// WithTopK 设置返回文档数量 +func WithTopK(k int) QueryOption { + return func(q *RAGQuery) { + q.TopK = k + } +} + +// WithContextMode 设置上下文模式 +func WithContextMode(mode string) QueryOption { + return func(q *RAGQuery) { + q.ContextMode = mode + } +} + +// WithFilters 设置过滤条件 +func WithFilters(filters map[string]interface{}) QueryOption { + return func(q *RAGQuery) { + q.Filters = filters + } +} diff --git a/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/internal/standalone/server.go b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/internal/standalone/server.go new file mode 100644 index 000000000..73e7c281c --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/internal/standalone/server.go @@ -0,0 +1,794 @@ +// MCP Server implementation for Nginx Migration Tools - Standalone Mode +package standalone + +import ( + "encoding/json" + "fmt" + "strings" + + "nginx-migration-mcp/tools" +) + +// NewMCPServer creates a new MCP server instance +func NewMCPServer(config *ServerConfig) *MCPServer { + return &MCPServer{config: config} +} + +// HandleMessage processes an incoming MCP message +func (s *MCPServer) HandleMessage(msg MCPMessage) MCPMessage { + switch msg.Method { + case "initialize": + return s.handleInitialize(msg) + case "tools/list": + return s.handleToolsList(msg) + case "tools/call": + return s.handleToolsCall(msg) + default: + return s.errorResponse(msg.ID, -32601, "Method not found") + } +} + +func (s *MCPServer) handleInitialize(msg MCPMessage) MCPMessage { + return MCPMessage{ + JSONRPC: "2.0", + ID: msg.ID, + Result: map[string]interface{}{ + "protocolVersion": "2024-11-05", + "capabilities": map[string]interface{}{ + "tools": map[string]interface{}{ + "listChanged": true, + }, + }, + "serverInfo": map[string]interface{}{ + "name": s.config.Server.Name, + "version": s.config.Server.Version, + }, + }, + } +} + +func (s *MCPServer) handleToolsList(msg MCPMessage) MCPMessage { + toolsList := tools.GetMCPTools() + + return MCPMessage{ + JSONRPC: "2.0", + ID: msg.ID, + Result: map[string]interface{}{ + "tools": toolsList, + }, + } +} + +func (s *MCPServer) handleToolsCall(msg MCPMessage) MCPMessage { + var params CallToolParams + paramsBytes, _ := json.Marshal(msg.Params) + json.Unmarshal(paramsBytes, ¶ms) + + handlers := tools.GetToolHandlers(s) + handler, exists := handlers[params.Name] + + if !exists { + return s.errorResponse(msg.ID, -32601, fmt.Sprintf("Unknown tool: %s", params.Name)) + } + + result := handler(params.Arguments) + + return MCPMessage{ + JSONRPC: "2.0", + ID: msg.ID, + Result: result, + } +} + +func (s *MCPServer) errorResponse(id interface{}, code int, message string) MCPMessage { + return MCPMessage{ + JSONRPC: "2.0", + ID: id, + Error: &MCPError{ + Code: code, + Message: message, + }, + } +} + +// Tool implementations + +func (s *MCPServer) parseNginxConfig(args map[string]interface{}) tools.ToolResult { + configContent, ok := args["config_content"].(string) + if !ok { + return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing config_content"}}} + } + + serverCount := strings.Count(configContent, "server {") + locationCount := strings.Count(configContent, "location") + hasSSL := strings.Contains(configContent, "ssl") + hasProxy := strings.Contains(configContent, "proxy_pass") + hasRewrite := strings.Contains(configContent, "rewrite") + + complexity := "Simple" + if serverCount > 1 || (hasRewrite && hasSSL) { + complexity = "Complex" + } else if hasRewrite || hasSSL { + complexity = "Medium" + } + + analysis := fmt.Sprintf(`Nginx配置分析结果 + +基础信息: +- Server块: %d个 +- Location块: %d个 +- SSL配置: %t +- 反向代理: %t +- URL重写: %t + +复杂度: %s + +迁移建议:`, serverCount, locationCount, hasSSL, hasProxy, hasRewrite, complexity) + + if hasProxy { + analysis += "\n- 反向代理将转换为HTTPRoute backendRefs" + } + if hasRewrite { + analysis += "\n- URL重写将使用URLRewrite过滤器" + } + if hasSSL { + analysis += "\n- SSL配置需要迁移到Gateway资源" + } + + return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: analysis}}} +} + +func (s *MCPServer) convertToHigress(args map[string]interface{}) tools.ToolResult { + configContent, ok := args["config_content"].(string) + if !ok { + return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing config_content"}}} + } + + namespace := s.config.Defaults.Namespace + if ns, ok := args["namespace"].(string); ok { + namespace = ns + } + + hostname := s.config.Defaults.Hostname + lines := strings.Split(configContent, "\n") + for _, line := range lines { + if strings.Contains(line, "server_name") && !strings.Contains(line, "#") { + parts := strings.Fields(line) + if len(parts) >= 2 { + hostname = strings.TrimSuffix(parts[1], ";") + break + } + } + } + + yamlConfig := fmt.Sprintf(`转换后的Higress配置 + +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: %s + namespace: %s + annotations: + higress.io/migrated-from: "nginx" +spec: + parentRefs: + - name: %s + namespace: %s + hostnames: + - %s + rules: + - matches: + - path: + type: PathPrefix + value: %s + backendRefs: + - name: %s + port: %d + +--- +apiVersion: v1 +kind: Service +metadata: + name: %s + namespace: %s +spec: + selector: + app: backend + ports: + - port: %d + targetPort: %d + +转换完成 + +应用步骤: +1. 保存为 higress-config.yaml +2. 执行: kubectl apply -f higress-config.yaml +3. 验证: kubectl get httproute -n %s`, + s.config.GenerateRouteName(hostname), namespace, + s.config.Gateway.Name, s.config.Gateway.Namespace, hostname, s.config.Defaults.PathPrefix, + s.config.GenerateServiceName(hostname), s.config.Service.DefaultPort, + s.config.GenerateServiceName(hostname), namespace, + s.config.Service.DefaultPort, s.config.Service.DefaultTarget, namespace) + + return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: yamlConfig}}} +} + +func (s *MCPServer) analyzeLuaPlugin(args map[string]interface{}) tools.ToolResult { + luaCode, ok := args["lua_code"].(string) + if !ok { + return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing lua_code"}}} + } + + // 使用新的 AI 友好分析 + analysis := tools.AnalyzeLuaPluginForAI(luaCode) + + // 生成用户友好的消息 + features := []string{} + for feature := range analysis.Features { + features = append(features, fmt.Sprintf("- %s", feature)) + } + + userMessage := fmt.Sprintf(`✅ Lua 插件分析完成 + +📊 **检测到的特性**: +%s + +⚠️ **兼容性警告**: +%s + +📈 **复杂度**:%s +🔄 **兼容性级别**:%s + +💡 **迁移建议**:`, + strings.Join(features, "\n"), + strings.Join(analysis.Warnings, "\n- "), + analysis.Complexity, + analysis.Compatibility, + ) + + switch analysis.Compatibility { + case "full": + userMessage += "\n- 可直接迁移到 WASM 插件\n- 建议使用工具链进行转换" + case "partial": + userMessage += "\n- 需要部分重构\n- 强烈建议使用工具链并让 AI 参与代码生成" + case "manual": + userMessage += "\n- 需要手动重写\n- 建议分步骤进行,使用工具链辅助" + } + + userMessage += "\n\n🔗 **后续操作**:\n" + userMessage += "1. 调用 `generate_conversion_hints` 工具获取详细的转换提示\n" + userMessage += "2. 基于提示生成 Go WASM 代码\n" + userMessage += "3. 调用 `validate_wasm_code` 工具验证生成的代码\n" + userMessage += "4. 调用 `generate_deployment_config` 工具生成部署配置\n" + userMessage += "\n或者直接使用 `convert_lua_to_wasm` 进行一键转换。" + + // 生成 AI 指令 + aiInstructions := fmt.Sprintf(`你现在已经获得了 Lua 插件的分析结果。基于这些信息,你可以: + +### 选项 1:使用工具链进行精细控制 + +调用 generate_conversion_hints 工具,传入以下分析结果: +`+"```json"+` +{ + "analysis_result": %s, + "plugin_name": "your-plugin-name" +} +`+"```"+` + +这将为你提供代码生成模板,然后基于模板生成 Go WASM 代码。 + +### 选项 2:一键转换 + +如果用户希望快速转换,可以直接调用 convert_lua_to_wasm 工具。 + +### 建议的对话流程 + +1. **询问用户**:是否需要详细的转换提示,还是直接生成代码? +2. **如果需要提示**:调用 generate_conversion_hints +3. **生成代码后**:询问是否需要验证(调用 validate_wasm_code) +4. **验证通过后**:询问是否需要生成部署配置(调用 generate_deployment_config) + +### 关键注意事项 + +%s + +### 代码生成要点 + +- 检测到的 Nginx 变量需要映射到 HTTP 头部 +- 复杂度为 %s,请相应调整代码结构 +- 兼容性级别为 %s,注意处理警告中的问题 +`, + string(mustMarshalJSON(analysis)), + formatWarningsForAI(analysis.Warnings), + analysis.Complexity, + analysis.Compatibility, + ) + + return tools.FormatToolResultWithAIContext(userMessage, aiInstructions, analysis) +} + +func mustMarshalJSON(v interface{}) []byte { + data, _ := json.Marshal(v) + return data +} + +func formatWarningsForAI(warnings []string) string { + if len(warnings) == 0 { + return "- 无特殊警告,可以直接转换" + } + result := []string{} + for _, w := range warnings { + result = append(result, fmt.Sprintf("- ⚠️ %s", w)) + } + return strings.Join(result, "\n") +} + +func (s *MCPServer) convertLuaToWasm(args map[string]interface{}) tools.ToolResult { + luaCode, ok := args["lua_code"].(string) + if !ok { + return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing lua_code"}}} + } + + pluginName, ok := args["plugin_name"].(string) + if !ok { + return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing plugin_name"}}} + } + + analyzer := tools.AnalyzeLuaScript(luaCode) + result, err := tools.ConvertLuaToWasm(analyzer, pluginName) + if err != nil { + return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: fmt.Sprintf("Error: %v", err)}}} + } + + response := fmt.Sprintf(`Lua脚本转换完成 + +转换分析: +- 复杂度: %s +- 检测特性: %d个 +- 兼容性警告: %d个 + +注意事项: +%s + +生成的文件: + +==== main.go ==== +%s + +==== WasmPlugin配置 ==== +%s + +部署步骤: +1. 创建插件目录: mkdir -p extensions/%s +2. 保存Go代码到: extensions/%s/main.go +3. 构建插件: PLUGIN_NAME=%s make build +4. 应用配置: kubectl apply -f wasmplugin.yaml + +提示: +- 请根据实际需求调整配置 +- 测试插件功能后再部署到生产环境 +- 如有共享状态需求,请配置Redis等外部存储 +`, + analyzer.Complexity, + len(analyzer.Features), + len(analyzer.Warnings), + strings.Join(analyzer.Warnings, "\n- "), + result.GoCode, + result.WasmPluginYAML, + pluginName, pluginName, pluginName) + + return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: response}}} +} + +// GenerateConversionHints 生成详细的代码转换提示 +func (s *MCPServer) GenerateConversionHints(args map[string]interface{}) tools.ToolResult { + analysisResultStr, ok := args["analysis_result"].(string) + if !ok { + return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing analysis_result"}}} + } + + pluginName, ok := args["plugin_name"].(string) + if !ok { + return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing plugin_name"}}} + } + + // 解析分析结果 + var analysis tools.AnalysisResultForAI + if err := json.Unmarshal([]byte(analysisResultStr), &analysis); err != nil { + return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: fmt.Sprintf("Error parsing analysis_result: %v", err)}}} + } + + // 生成转换提示 + hints := tools.GenerateConversionHints(analysis, pluginName) + + // 格式化输出 + userMessage := fmt.Sprintf(`🎯 代码转换提示 + +**插件名称**: %s +**代码模板**: %s + +%s +`, + pluginName, + hints.CodeTemplate, + func() string { + if len(hints.Warnings) > 0 { + return "⚠️ **警告**: " + formatWarningsListForUser(hints.Warnings) + } + return "" + }(), + ) + + // 生成详细的 AI 指令 + aiInstructions := fmt.Sprintf(`现在你需要基于以下信息生成 Go WASM 插件代码。 + +## 代码模板 + +%s + +## 生成代码的要求 + +1. **实现所需的回调函数** +2. **保持 Lua 代码的业务逻辑等价** +3. **添加适当的错误处理** +4. **包含配置解析逻辑(如需要)** + +## 输出格式 + +请按以下格式输出代码: + +### main.go +`+"```go"+` +[完整的 Go 代码] +`+"```"+` + +生成代码后,建议调用 validate_wasm_code 工具进行验证。 +`, + hints.CodeTemplate, + ) + + return tools.FormatToolResultWithAIContext(userMessage, aiInstructions, hints) +} + +// ValidateWasmCode 验证生成的 Go WASM 代码 +func (s *MCPServer) ValidateWasmCode(args map[string]interface{}) tools.ToolResult { + goCode, ok := args["go_code"].(string) + if !ok { + return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing go_code"}}} + } + + pluginName, ok := args["plugin_name"].(string) + if !ok { + return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing plugin_name"}}} + } + + // 执行验证 + report := tools.ValidateWasmCode(goCode, pluginName) + + // 统计各类问题数量 + requiredCount := 0 + recommendedCount := 0 + optionalCount := 0 + bestPracticeCount := 0 + + for _, issue := range report.Issues { + switch issue.Category { + case "required": + requiredCount++ + case "recommended": + recommendedCount++ + case "optional": + optionalCount++ + case "best_practice": + bestPracticeCount++ + } + } + + // 构建用户消息 + userMessage := fmt.Sprintf(`## 代码验证报告 + +%s + +### 发现的回调函数 (%d 个) +%s + +### 配置结构 +%s + +### 问题分类 + +#### 必须修复 (%d 个) +%s + +#### 建议修复 (%d 个) +%s + +#### 可选优化 (%d 个) +%s + +#### 最佳实践 (%d 个) +%s + +### 缺失的导入包 (%d 个) +%s + +--- + +`, + report.Summary, + len(report.FoundCallbacks), + formatCallbacksList(report.FoundCallbacks), + formatConfigStatus(report.HasConfig), + requiredCount, + formatIssuesByCategory(report.Issues, "required"), + recommendedCount, + formatIssuesByCategory(report.Issues, "recommended"), + optionalCount, + formatIssuesByCategory(report.Issues, "optional"), + bestPracticeCount, + formatIssuesByCategory(report.Issues, "best_practice"), + len(report.MissingImports), + formatList(report.MissingImports), + ) + + // 根据问题级别给出建议 + hasRequired := requiredCount > 0 + if hasRequired { + userMessage += " **请优先修复 \"必须修复\" 的问题,否则代码可能无法编译或运行。**\n\n" + } else if recommendedCount > 0 { + userMessage += " **代码基本结构正确。** 建议修复 \"建议修复\" 的问题以提高代码质量。\n\n" + } else { + userMessage += " **代码验证通过!** 可以继续生成部署配置。\n\n" + userMessage += "**下一步**:调用 `generate_deployment_config` 工具生成部署配置。\n" + } + + // AI 指令 + aiInstructions := "" + if hasRequired { + aiInstructions = `代码验证发现必须修复的问题。 + +## 修复指南 + +` + formatIssuesForAI(report.Issues, "required") + ` + +请修复上述问题后,再次调用 validate_wasm_code 工具进行验证。 +` + } else if recommendedCount > 0 { + aiInstructions = `代码基本结构正确,建议修复以下问题: + +` + formatIssuesForAI(report.Issues, "recommended") + ` + +可以选择修复这些问题,或直接调用 generate_deployment_config 工具生成部署配置。 +` + } else { + aiInstructions = `代码验证通过! + +## 下一步 + +调用 generate_deployment_config 工具,参数: +` + "```json" + ` +{ + "plugin_name": "` + pluginName + `", + "go_code": "[验证通过的代码]", + "namespace": "higress-system" +} +` + "```" + ` + +这将生成完整的部署配置包。 +` + } + + return tools.FormatToolResultWithAIContext(userMessage, aiInstructions, report) +} + +// GenerateDeploymentConfig 生成部署配置 +func (s *MCPServer) GenerateDeploymentConfig(args map[string]interface{}) tools.ToolResult { + pluginName, ok := args["plugin_name"].(string) + if !ok { + return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing plugin_name"}}} + } + + goCode, ok := args["go_code"].(string) + if !ok { + return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing go_code"}}} + } + + namespace := "higress-system" + if ns, ok := args["namespace"].(string); ok && ns != "" { + namespace = ns + } + + configSchema := "" + if cs, ok := args["config_schema"].(string); ok { + configSchema = cs + } + + // 生成部署包 + pkg := tools.GenerateDeploymentPackage(pluginName, goCode, configSchema, namespace) + + // 格式化输出 + userMessage := fmt.Sprintf(`🎉 部署配置生成完成! + +已为插件 **%s** 生成完整的部署配置包。 + +## 生成的文件 + +### 1. WasmPlugin 配置 +- 文件名:wasmplugin.yaml +- 命名空间:%s +- 包含默认配置和匹配规则 + +### 2. 构建脚本 +- Makefile:自动化构建和部署 +- Dockerfile:容器化打包 + +### 3. 文档 +- README.md:完整的使用说明 +- 包含快速开始、配置说明、问题排查 + +### 4. 测试脚本 +- test.sh:自动化测试脚本 + +### 5. 依赖清单 +- 列出了所有必需的 Go 模块 + +--- + +## 快速部署 + +`+"```bash"+` +# 1. 保存文件 +# 保存 main.go +# 保存 wasmplugin.yaml +# 保存 Makefile +# 保存 Dockerfile + +# 2. 构建插件 +make build + +# 3. 构建并推送镜像 +make docker-build docker-push + +# 4. 部署到 Kubernetes +make deploy + +# 5. 验证部署 +kubectl get wasmplugin -n %s +`+"```"+` + +--- + +**文件内容请见下方结构化数据部分。** +`, + pluginName, + namespace, + namespace, + ) + + aiInstructions := fmt.Sprintf(`部署配置已生成完毕。 + +## 向用户展示文件 + +请将以下文件内容清晰地展示给用户: + +### 1. main.go +用户已经有这个文件。 + +### 2. wasmplugin.yaml +`+"```yaml"+` +%s +`+"```"+` + +### 3. Makefile +`+"```makefile"+` +%s +`+"```"+` + +### 4. Dockerfile +`+"```dockerfile"+` +%s +`+"```"+` + +### 5. README.md +`+"```markdown"+` +%s +`+"```"+` + +### 6. test.sh +`+"```bash"+` +%s +`+"```"+` + +## 后续支持 + +询问用户是否需要: +1. 解释任何配置项的含义 +2. 自定义某些配置 +3. 帮助解决部署问题 +`, + pkg.WasmPluginYAML, + pkg.Makefile, + pkg.Dockerfile, + pkg.README, + pkg.TestScript, + ) + + return tools.FormatToolResultWithAIContext(userMessage, aiInstructions, pkg) +} + +// 辅助格式化函数 + +func formatWarningsListForUser(warnings []string) string { + if len(warnings) == 0 { + return "无" + } + return strings.Join(warnings, "\n- ") +} + +func formatCallbacksList(callbacks []string) string { + if len(callbacks) == 0 { + return "无" + } + return "- " + strings.Join(callbacks, "\n- ") +} + +func formatConfigStatus(hasConfig bool) string { + if hasConfig { + return " 已定义配置结构体" + } + return "- 未定义配置结构体(如不需要配置可忽略)" +} + +func formatIssuesByCategory(issues []tools.ValidationIssue, category string) string { + var filtered []string + for _, issue := range issues { + if issue.Category == category { + filtered = append(filtered, fmt.Sprintf("- **[%s]** %s\n 💡 建议: %s\n 📌 影响: %s", + issue.Type, issue.Message, issue.Suggestion, issue.Impact)) + } + } + if len(filtered) == 0 { + return "无" + } + return strings.Join(filtered, "\n\n") +} + +func formatIssuesForAI(issues []tools.ValidationIssue, category string) string { + var filtered []tools.ValidationIssue + for _, issue := range issues { + if issue.Category == category { + filtered = append(filtered, issue) + } + } + + if len(filtered) == 0 { + return "无问题" + } + + result := []string{} + for i, issue := range filtered { + result = append(result, fmt.Sprintf(` +### 问题 %d: %s + +**类型**: %s +**建议**: %s +**影响**: %s + +请根据建议修复此问题。 +`, + i+1, + issue.Message, + issue.Type, + issue.Suggestion, + issue.Impact, + )) + } + return strings.Join(result, "\n") +} + +func formatList(items []string) string { + if len(items) == 0 { + return "无" + } + return "- " + strings.Join(items, "\n- ") +} diff --git a/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/mcp-tools.json b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/mcp-tools.json new file mode 100644 index 000000000..6a5b890a6 --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/mcp-tools.json @@ -0,0 +1,140 @@ +{ + "version": "2.0.0", + "name": "nginx-migration", + "description": "Nginx 到 Higress 迁移工具集:支持配置转换和 Lua 插件迁移", + "tools": [ + { + "name": "parse_nginx_config", + "description": "解析和分析 Nginx 配置文件,识别配置结构和复杂度", + "inputSchema": { + "type": "object", + "properties": { + "config_content": { + "type": "string", + "description": "Nginx 配置文件内容" + } + }, + "required": ["config_content"] + } + }, + { + "name": "convert_to_higress", + "description": "智能解析 Nginx 配置并通过 AI 推理生成 Higress Ingress/HTTPRoute 配置。支持复杂配置(SSL、重写、重定向、upstream 等)的智能转换,结合 RAG 知识库提供准确的转换方案", + "inputSchema": { + "type": "object", + "properties": { + "config_content": { + "type": "string", + "description": "Nginx 配置文件内容" + }, + "namespace": { + "type": "string", + "description": "目标 Kubernetes 命名空间", + "default": "default" + }, + "use_gateway_api": { + "type": "boolean", + "description": "是否使用 Gateway API (HTTPRoute)。默认 false,使用 Ingress", + "default": false + } + }, + "required": ["config_content"] + } + }, + { + "name": "analyze_lua_plugin", + "description": "分析 Nginx Lua 插件的兼容性,识别使用的 API 和潜在迁移问题,返回结构化分析结果供后续工具使用", + "inputSchema": { + "type": "object", + "properties": { + "lua_code": { + "type": "string", + "description": "Nginx Lua 插件代码" + } + }, + "required": ["lua_code"] + } + }, + { + "name": "generate_conversion_hints", + "description": "基于 Lua 分析结果生成代码转换模板", + "inputSchema": { + "type": "object", + "properties": { + "analysis_result": { + "type": "string", + "description": "analyze_lua_plugin 返回的 JSON 格式分析结果" + }, + "plugin_name": { + "type": "string", + "description": "目标插件名称(小写字母和连字符)" + } + }, + "required": ["analysis_result", "plugin_name"] + } + }, + { + "name": "validate_wasm_code", + "description": "验证生成的 Go WASM 插件代码,检查语法、API 使用、配置结构等,输出验证报告和改进建议", + "inputSchema": { + "type": "object", + "properties": { + "go_code": { + "type": "string", + "description": "生成的 Go WASM 插件代码" + }, + "plugin_name": { + "type": "string", + "description": "插件名称" + } + }, + "required": ["go_code", "plugin_name"] + } + }, + { + "name": "generate_deployment_config", + "description": "为验证通过的 WASM 插件生成完整的部署配置包,包括 WasmPlugin YAML、Makefile、Dockerfile、README 和测试脚本", + "inputSchema": { + "type": "object", + "properties": { + "plugin_name": { + "type": "string", + "description": "插件名称" + }, + "go_code": { + "type": "string", + "description": "验证通过的 Go 代码" + }, + "config_schema": { + "type": "string", + "description": "配置 JSON Schema(可选)" + }, + "namespace": { + "type": "string", + "description": "部署命名空间", + "default": "higress-system" + } + }, + "required": ["plugin_name", "go_code"] + } + }, + { + "name": "convert_lua_to_wasm", + "description": "一键将 Nginx Lua 脚本转换为 Higress WASM 插件,自动生成 Go 代码和 WasmPlugin 配置。适合简单插件快速转换", + "inputSchema": { + "type": "object", + "properties": { + "lua_code": { + "type": "string", + "description": "要转换的 Nginx Lua 插件代码" + }, + "plugin_name": { + "type": "string", + "description": "生成的 WASM 插件名称 (小写字母和连字符)" + } + }, + "required": ["lua_code", "plugin_name"] + } + } + ] +} diff --git a/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/standalone/cmd/main.go b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/standalone/cmd/main.go new file mode 100644 index 000000000..26b597800 --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/standalone/cmd/main.go @@ -0,0 +1,45 @@ +package main + +import ( + "bufio" + "encoding/json" + "fmt" + "log" + "os" + + "nginx-migration-mcp/standalone" +) + +const Version = "1.0.0" + +func main() { + // Load config + config := standalone.LoadConfig() + + server := standalone.NewMCPServer(config) + + scanner := bufio.NewScanner(os.Stdin) + writer := bufio.NewWriter(os.Stdout) + + for scanner.Scan() { + line := scanner.Bytes() + + var msg standalone.MCPMessage + if err := json.Unmarshal(line, &msg); err != nil { + log.Printf("Error parsing message: %v", err) + continue + } + + response := server.HandleMessage(msg) + responseBytes, _ := json.Marshal(response) + + writer.Write(responseBytes) + writer.WriteByte('\n') + writer.Flush() + } + + if err := scanner.Err(); err != nil { + fmt.Fprintf(os.Stderr, "Error reading from stdin: %v\n", err) + os.Exit(1) + } +} diff --git a/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/standalone/config.go b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/standalone/config.go new file mode 100644 index 000000000..590db51ce --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/standalone/config.go @@ -0,0 +1,141 @@ +// Configuration management for nginx migration MCP server - Standalone Mode +package standalone + +import ( + "encoding/json" + "fmt" + "os" + "strconv" + "strings" +) + +// ServerConfig holds all configurable values +type ServerConfig struct { + Server ServerSettings `json:"server"` + Gateway GatewaySettings `json:"gateway"` + Service ServiceSettings `json:"service"` + Defaults DefaultSettings `json:"defaults"` +} + +type ServerSettings struct { + Name string `json:"name"` + Version string `json:"version"` + Port string `json:"port"` + APIBaseURL string `json:"api_base_url"` +} + +type GatewaySettings struct { + Name string `json:"name"` + Namespace string `json:"namespace"` +} + +type ServiceSettings struct { + DefaultName string `json:"default_name"` + DefaultPort int `json:"default_port"` + DefaultTarget int `json:"default_target_port"` +} + +type DefaultSettings struct { + Hostname string `json:"hostname"` + Namespace string `json:"namespace"` + PathPrefix string `json:"path_prefix"` + RoutePrefix string `json:"route_prefix"` +} + +// LoadConfig loads configuration from environment variables and files +func LoadConfig() *ServerConfig { + config := &ServerConfig{ + Server: ServerSettings{ + Name: getEnvOrDefault("NGINX_MCP_SERVER_NAME", "nginx-migration-mcp"), + Version: getEnvOrDefault("NGINX_MCP_VERSION", "1.0.0"), + Port: getEnvOrDefault("NGINX_MCP_PORT", "8080"), + APIBaseURL: getEnvOrDefault("NGINX_MIGRATION_API_URL", "http://localhost:8080"), + }, + Gateway: GatewaySettings{ + Name: getEnvOrDefault("HIGRESS_GATEWAY_NAME", "higress-gateway"), + Namespace: getEnvOrDefault("HIGRESS_GATEWAY_NAMESPACE", "higress-system"), + }, + Service: ServiceSettings{ + DefaultName: getEnvOrDefault("DEFAULT_SERVICE_NAME", "backend-service"), + DefaultPort: getIntEnvOrDefault("DEFAULT_SERVICE_PORT", 80), + DefaultTarget: getIntEnvOrDefault("DEFAULT_TARGET_PORT", 8080), + }, + Defaults: DefaultSettings{ + Hostname: getEnvOrDefault("DEFAULT_HOSTNAME", "example.com"), + Namespace: getEnvOrDefault("DEFAULT_NAMESPACE", "default"), + PathPrefix: getEnvOrDefault("DEFAULT_PATH_PREFIX", "/"), + RoutePrefix: getEnvOrDefault("ROUTE_NAME_PREFIX", "nginx-migrated"), + }, + } + + // Try to load from config file if exists + if configFile := os.Getenv("NGINX_MCP_CONFIG_FILE"); configFile != "" { + if err := loadConfigFromFile(config, configFile); err != nil { + fmt.Printf("Warning: Failed to load config from %s: %v\n", configFile, err) + } + } + + return config +} + +// loadConfigFromFile loads configuration from JSON file +func loadConfigFromFile(config *ServerConfig, filename string) error { + data, err := os.ReadFile(filename) + if err != nil { + return err + } + + return json.Unmarshal(data, config) +} + +// getEnvOrDefault returns environment variable value or default +func getEnvOrDefault(key, defaultValue string) string { + if value := os.Getenv(key); value != "" { + return value + } + return defaultValue +} + +// getIntEnvOrDefault returns environment variable as int or default +func getIntEnvOrDefault(key string, defaultValue int) int { + if value := os.Getenv(key); value != "" { + if intValue, err := strconv.Atoi(value); err == nil { + return intValue + } + } + return defaultValue +} + +// GenerateRouteName generates a unique route name +func (c *ServerConfig) GenerateRouteName(hostname string) string { + if hostname == "" || hostname == c.Defaults.Hostname { + return fmt.Sprintf("%s-route", c.Defaults.RoutePrefix) + } + // Replace dots and special characters for valid k8s name + safeName := hostname + for _, char := range []string{".", "_", ":"} { + safeName = strings.ReplaceAll(safeName, char, "-") + } + return fmt.Sprintf("%s-%s", c.Defaults.RoutePrefix, safeName) +} + +// GenerateIngressName generates a unique ingress name +func (c *ServerConfig) GenerateIngressName(hostname string) string { + if hostname == "" || hostname == c.Defaults.Hostname { + return fmt.Sprintf("%s-ingress", c.Defaults.RoutePrefix) + } + // Replace dots and special characters for valid k8s name + safeName := hostname + for _, char := range []string{".", "_", ":"} { + safeName = strings.ReplaceAll(safeName, char, "-") + } + return fmt.Sprintf("%s-%s", c.Defaults.RoutePrefix, safeName) +} + +// GenerateServiceName generates service name based on hostname +func (c *ServerConfig) GenerateServiceName(hostname string) string { + if hostname == "" || hostname == c.Defaults.Hostname { + return c.Service.DefaultName + } + return fmt.Sprintf("%s-service", hostname) +} diff --git a/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/standalone/server.go b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/standalone/server.go new file mode 100644 index 000000000..7f5a4ab5a --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/standalone/server.go @@ -0,0 +1,914 @@ +// Package standalone implements MCP Server for Nginx Migration Tools in standalone mode. +package standalone + +import ( + "encoding/json" + "fmt" + "log" + "os" + "path/filepath" + "strings" + + "nginx-migration-mcp/internal/rag" + "nginx-migration-mcp/tools" +) + +// NewMCPServer creates a new MCP server instance +func NewMCPServer(config *ServerConfig) *MCPServer { + // 初始化 RAG 管理器 + // 获取可执行文件所在目录 + execPath, err := os.Executable() + if err != nil { + log.Printf("WARNING: Failed to get executable path: %v", err) + execPath = "." + } + execDir := filepath.Dir(execPath) + + // 尝试多个可能的配置文件路径(相对于可执行文件) + ragConfigPaths := []string{ + filepath.Join(execDir, "config", "rag.json"), // 同级 config 目录 + filepath.Join(execDir, "..", "config", "rag.json"), // 上级 config 目录 + "config/rag.json", // 当前工作目录 + } + + var ragConfig *rag.RAGConfig + var configErr error + + for _, path := range ragConfigPaths { + ragConfig, configErr = rag.LoadRAGConfig(path) + if configErr == nil { + log.Printf("Loaded RAG config from: %s", path) + break + } + } + + if configErr != nil { + log.Printf("WARNING: Failed to load RAG config: %v, RAG will be disabled", configErr) + ragConfig = &rag.RAGConfig{Enabled: false} + } + + ragManager := rag.NewRAGManager(ragConfig) + + if ragManager.IsEnabled() { + log.Printf("RAG Manager initialized and enabled") + } else { + log.Printf("RAG Manager disabled, using rule-based approach") + } + + return &MCPServer{ + config: config, + ragManager: ragManager, + } +} + +// HandleMessage processes an incoming MCP message +func (s *MCPServer) HandleMessage(msg MCPMessage) MCPMessage { + switch msg.Method { + case "initialize": + return s.handleInitialize(msg) + case "tools/list": + return s.handleToolsList(msg) + case "tools/call": + return s.handleToolsCall(msg) + default: + return s.errorResponse(msg.ID, -32601, "Method not found") + } +} + +func (s *MCPServer) handleInitialize(msg MCPMessage) MCPMessage { + return MCPMessage{ + JSONRPC: "2.0", + ID: msg.ID, + Result: map[string]interface{}{ + "protocolVersion": "2024-11-05", + "capabilities": map[string]interface{}{ + "tools": map[string]interface{}{ + "listChanged": true, + }, + }, + "serverInfo": map[string]interface{}{ + "name": s.config.Server.Name, + "version": s.config.Server.Version, + }, + }, + } +} + +func (s *MCPServer) handleToolsList(msg MCPMessage) MCPMessage { + toolsList := tools.GetMCPTools() + + return MCPMessage{ + JSONRPC: "2.0", + ID: msg.ID, + Result: map[string]interface{}{ + "tools": toolsList, + }, + } +} + +func (s *MCPServer) handleToolsCall(msg MCPMessage) MCPMessage { + var params CallToolParams + paramsBytes, _ := json.Marshal(msg.Params) + json.Unmarshal(paramsBytes, ¶ms) + + handlers := tools.GetToolHandlers(s) + handler, exists := handlers[params.Name] + + if !exists { + return s.errorResponse(msg.ID, -32601, fmt.Sprintf("Unknown tool: %s", params.Name)) + } + + result := handler(params.Arguments) + + return MCPMessage{ + JSONRPC: "2.0", + ID: msg.ID, + Result: result, + } +} + +func (s *MCPServer) errorResponse(id interface{}, code int, message string) MCPMessage { + return MCPMessage{ + JSONRPC: "2.0", + ID: id, + Error: &MCPError{ + Code: code, + Message: message, + }, + } +} + +// Tool implementations + +func (s *MCPServer) ParseNginxConfig(args map[string]interface{}) tools.ToolResult { + configContent, ok := args["config_content"].(string) + if !ok { + return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing config_content"}}} + } + + serverCount := strings.Count(configContent, "server {") + locationCount := strings.Count(configContent, "location") + hasSSL := strings.Contains(configContent, "ssl") + hasProxy := strings.Contains(configContent, "proxy_pass") + hasRewrite := strings.Contains(configContent, "rewrite") + + complexity := "Simple" + if serverCount > 1 || (hasRewrite && hasSSL) { + complexity = "Complex" + } else if hasRewrite || hasSSL { + complexity = "Medium" + } + + analysis := fmt.Sprintf(`Nginx配置分析结果 + +基础信息: +- Server块: %d个 +- Location块: %d个 +- SSL配置: %t +- 反向代理: %t +- URL重写: %t + +复杂度: %s + +迁移建议:`, serverCount, locationCount, hasSSL, hasProxy, hasRewrite, complexity) + + if hasProxy { + analysis += "\n- 反向代理将转换为Ingress backend配置" + } + if hasRewrite { + analysis += "\n- URL重写将使用Higress注解 (higress.io/rewrite-target)" + } + if hasSSL { + analysis += "\n- SSL配置将转换为Ingress TLS配置" + } + + return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: analysis}}} +} + +func (s *MCPServer) ConvertToHigress(args map[string]interface{}) tools.ToolResult { + configContent, ok := args["config_content"].(string) + if !ok { + return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing config_content"}}} + } + + namespace := s.config.Defaults.Namespace + if ns, ok := args["namespace"].(string); ok { + namespace = ns + } + + // 检查是否使用 Gateway API + useGatewayAPI := false + if val, ok := args["use_gateway_api"].(bool); ok { + useGatewayAPI = val + } + + // === 使用增强的解析器解析 Nginx 配置 === + nginxConfig, err := tools.ParseNginxConfig(configContent) + if err != nil { + return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: fmt.Sprintf("Error parsing Nginx config: %v", err)}}} + } + + // 分析配置 + analysis := tools.AnalyzeNginxConfig(nginxConfig) + + // === RAG 增强:查询转换示例和最佳实践 === + var ragContext string + if s.ragManager != nil && s.ragManager.IsEnabled() { + // 构建查询关键词 + queryBuilder := []string{"Nginx 配置转换到 Higress"} + + if useGatewayAPI { + queryBuilder = append(queryBuilder, "Gateway API HTTPRoute") + } else { + queryBuilder = append(queryBuilder, "Kubernetes Ingress") + } + + // 根据特性添加查询关键词 + if analysis.Features["ssl"] { + queryBuilder = append(queryBuilder, "SSL TLS 证书配置") + } + if analysis.Features["rewrite"] { + queryBuilder = append(queryBuilder, "URL 重写 rewrite 规则") + } + if analysis.Features["redirect"] { + queryBuilder = append(queryBuilder, "重定向 redirect") + } + if analysis.Features["header_manipulation"] { + queryBuilder = append(queryBuilder, "请求头 响应头处理") + } + if len(nginxConfig.Upstreams) > 0 { + queryBuilder = append(queryBuilder, "负载均衡 upstream") + } + + queryString := strings.Join(queryBuilder, " ") + log.Printf("RAG Query: %s", queryString) + + ragResult, err := s.ragManager.QueryForTool( + "convert_to_higress", + queryString, + "nginx_to_higress", + ) + + if err == nil && ragResult.Enabled && len(ragResult.Documents) > 0 { + log.Printf("RAG: Found %d documents for conversion", len(ragResult.Documents)) + ragContext = "\n\n## 参考文档(来自知识库)\n\n" + ragResult.FormatContextForAI() + } else { + if err != nil { + log.Printf("WARNING: RAG query failed: %v", err) + } + } + } + + // === 将配置数据转换为 JSON 供 AI 使用 === + configJSON, _ := json.MarshalIndent(nginxConfig, "", " ") + analysisJSON, _ := json.MarshalIndent(analysis, "", " ") + + // === 构建返回消息 === + userMessage := fmt.Sprintf(`📋 Nginx 配置解析完成 + +## 配置概览 +- Server 块: %d +- Location 块: %d +- 域名: %d 个 +- 复杂度: %s +- 目标格式: %s +- 命名空间: %s + +## 检测到的特性 +%s + +## 迁移建议 +%s +%s + +--- + +## Nginx 配置结构 + +`+"```json"+` +%s +`+"```"+` + +## 分析结果 + +`+"```json"+` +%s +`+"```"+` +%s +`, + analysis.ServerCount, + analysis.LocationCount, + analysis.DomainCount, + analysis.Complexity, + func() string { + if useGatewayAPI { + return "Gateway API (HTTPRoute)" + } + return "Kubernetes Ingress" + }(), + namespace, + formatFeatures(analysis.Features), + formatSuggestions(analysis.Suggestions), + func() string { + if ragContext != "" { + return "\n\n已加载知识库参考文档" + } + return "" + }(), + string(configJSON), + string(analysisJSON), + ragContext, + ) + + return tools.FormatToolResultWithAIContext(userMessage, "", map[string]interface{}{ + "nginx_config": nginxConfig, + "analysis": analysis, + "namespace": namespace, + "use_gateway_api": useGatewayAPI, + }) +} + +func (s *MCPServer) AnalyzeLuaPlugin(args map[string]interface{}) tools.ToolResult { + luaCode, ok := args["lua_code"].(string) + if !ok { + return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing lua_code"}}} + } + + // 使用新的 AI 友好分析 + analysis := tools.AnalyzeLuaPluginForAI(luaCode) + + // === RAG 增强:查询知识库获取转换建议 === + var ragContext string + if s.ragManager != nil && s.ragManager.IsEnabled() && len(analysis.APICalls) > 0 { + query := fmt.Sprintf("Nginx Lua API %s 在 Higress WASM 中的转换方法和最佳实践", strings.Join(analysis.APICalls, ", ")) + log.Printf("🔍 RAG Query: %s", query) + + ragResult, err := s.ragManager.QueryForTool("analyze_lua_plugin", query, "lua_migration") + if err == nil && ragResult.Enabled && len(ragResult.Documents) > 0 { + log.Printf("RAG: Found %d documents for Lua analysis", len(ragResult.Documents)) + ragContext = "\n\n## 知识库参考资料\n\n" + ragResult.FormatContextForAI() + } else if err != nil { + log.Printf(" RAG query failed: %v", err) + } + } + + // 生成用户友好的消息 + features := []string{} + for feature := range analysis.Features { + features = append(features, fmt.Sprintf("- %s", feature)) + } + + userMessage := fmt.Sprintf(`Lua 插件分析完成 + +## 检测到的特性 +%s + +## 基本信息 +- **复杂度**: %s +- **兼容性**: %s + +## 兼容性警告 +%s +%s + +## 后续操作 +- 调用 generate_conversion_hints 获取转换提示 +- 或直接使用 convert_lua_to_wasm 一键转换 + +## 分析结果 + +`+"```json"+` +%s +`+"```"+` +`, + strings.Join(features, "\n"), + analysis.Complexity, + analysis.Compatibility, + func() string { + if len(analysis.Warnings) > 0 { + return "- " + strings.Join(analysis.Warnings, "\n- ") + } + return "无" + }(), + ragContext, + string(mustMarshalJSON(analysis)), + ) + + return tools.FormatToolResultWithAIContext(userMessage, "", analysis) +} + +func mustMarshalJSON(v interface{}) []byte { + data, _ := json.Marshal(v) + return data +} + +func (s *MCPServer) ConvertLuaToWasm(args map[string]interface{}) tools.ToolResult { + luaCode, ok := args["lua_code"].(string) + if !ok { + return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing lua_code"}}} + } + + pluginName, ok := args["plugin_name"].(string) + if !ok { + return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing plugin_name"}}} + } + + analyzer := tools.AnalyzeLuaScript(luaCode) + result, err := tools.ConvertLuaToWasm(analyzer, pluginName) + if err != nil { + return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: fmt.Sprintf("Error: %v", err)}}} + } + + response := fmt.Sprintf(`Lua脚本转换完成 + +转换分析: +- 复杂度: %s +- 检测特性: %d个 +- 兼容性警告: %d个 + +注意事项: +%s + +生成的文件: + +==== main.go ==== +%s + +==== WasmPlugin配置 ==== +%s + +部署步骤: +1. 创建插件目录: mkdir -p extensions/%s +2. 保存Go代码到: extensions/%s/main.go +3. 构建插件: PLUGIN_NAME=%s make build +4. 应用配置: kubectl apply -f wasmplugin.yaml + +提示: +- 请根据实际需求调整配置 +- 测试插件功能后再部署到生产环境 +- 如有共享状态需求,请配置Redis等外部存储 +`, + analyzer.Complexity, + len(analyzer.Features), + len(analyzer.Warnings), + strings.Join(analyzer.Warnings, "\n- "), + result.GoCode, + result.WasmPluginYAML, + pluginName, pluginName, pluginName) + + return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: response}}} +} + +// GenerateConversionHints 生成详细的代码转换提示 +func (s *MCPServer) GenerateConversionHints(args map[string]interface{}) tools.ToolResult { + analysisResultStr, ok := args["analysis_result"].(string) + if !ok { + return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing analysis_result"}}} + } + + pluginName, ok := args["plugin_name"].(string) + if !ok { + return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing plugin_name"}}} + } + + // 解析分析结果 + var analysis tools.AnalysisResultForAI + if err := json.Unmarshal([]byte(analysisResultStr), &analysis); err != nil { + return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: fmt.Sprintf("Error parsing analysis_result: %v", err)}}} + } + + // 生成转换提示 + hints := tools.GenerateConversionHints(analysis, pluginName) + + // === RAG 增强:查询 Nginx API 转换文档 === + var ragDocs string + + // 构建更精确的查询语句 + queryBuilder := []string{} + if len(analysis.APICalls) > 0 { + queryBuilder = append(queryBuilder, "Nginx Lua API 转换到 Higress WASM") + + // 针对不同的 API 类型使用不同的查询关键词 + hasHeaderOps := analysis.Features["header_manipulation"] || analysis.Features["request_headers"] || analysis.Features["response_headers"] + hasBodyOps := analysis.Features["request_body"] || analysis.Features["response_body"] + hasResponseControl := analysis.Features["response_control"] + + if hasHeaderOps { + queryBuilder = append(queryBuilder, "请求头和响应头处理") + } + if hasBodyOps { + queryBuilder = append(queryBuilder, "请求体和响应体处理") + } + if hasResponseControl { + queryBuilder = append(queryBuilder, "响应控制和状态码设置") + } + + // 添加具体的 API 调用 + if len(analysis.APICalls) > 0 && len(analysis.APICalls) <= 5 { + queryBuilder = append(queryBuilder, fmt.Sprintf("涉及 API: %s", strings.Join(analysis.APICalls, ", "))) + } + } else { + queryBuilder = append(queryBuilder, "Higress WASM 插件开发 基础示例 Go SDK 使用") + } + + // 添加复杂度相关的查询 + if analysis.Complexity == "high" { + queryBuilder = append(queryBuilder, "复杂插件实现 高级功能") + } + + queryString := strings.Join(queryBuilder, " ") + + // 只有当 RAG 启用时才查询 + if s.ragManager != nil && s.ragManager.IsEnabled() { + log.Printf(" RAG Query: %s", queryString) + + ragContext, err := s.ragManager.QueryForTool( + "generate_conversion_hints", + queryString, + "lua_migration", + ) + + if err == nil && ragContext.Enabled && len(ragContext.Documents) > 0 { + log.Printf("RAG: Found %d documents for conversion hints", len(ragContext.Documents)) + ragDocs = "\n\n## 参考文档(来自知识库)\n\n" + ragContext.FormatContextForAI() + } else { + if err != nil { + log.Printf(" RAG query failed: %v", err) + } + ragDocs = "" + } + } else { + ragDocs = "" + } + + // 格式化输出 + userMessage := fmt.Sprintf(` 代码转换提示 + +**插件名称**: %s +**复杂度**: %s +**兼容性**: %s +%s + +## 代码模板 + +%s +%s +`, + pluginName, + analysis.Complexity, + analysis.Compatibility, + func() string { + if len(hints.Warnings) > 0 { + return "\n**警告**: " + formatWarningsListForUser(hints.Warnings) + } + return "" + }(), + hints.CodeTemplate, + ragDocs, + ) + + return tools.FormatToolResultWithAIContext(userMessage, "", hints) +} + +// ValidateWasmCode 验证生成的 Go WASM 代码 +func (s *MCPServer) ValidateWasmCode(args map[string]interface{}) tools.ToolResult { + goCode, ok := args["go_code"].(string) + if !ok { + return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing go_code"}}} + } + + pluginName, ok := args["plugin_name"].(string) + if !ok { + return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing plugin_name"}}} + } + + // 执行验证 + report := tools.ValidateWasmCode(goCode, pluginName) + + // 统计各类问题数量 + requiredCount := 0 + recommendedCount := 0 + optionalCount := 0 + bestPracticeCount := 0 + + for _, issue := range report.Issues { + switch issue.Category { + case "required": + requiredCount++ + case "recommended": + recommendedCount++ + case "optional": + optionalCount++ + case "best_practice": + bestPracticeCount++ + } + } + + // 构建用户消息 + userMessage := fmt.Sprintf(`## 代码验证报告 + +%s + +### 发现的回调函数 (%d 个) +%s + +### 配置结构 +%s + +### 问题分类 + +#### 必须修复 (%d 个) +%s + +#### 建议修复 (%d 个) +%s + +#### 可选优化 (%d 个) +%s + +#### 最佳实践 (%d 个) +%s + +### 缺失的导入包 (%d 个) +%s + +--- + +`, + report.Summary, + len(report.FoundCallbacks), + formatCallbacksList(report.FoundCallbacks), + formatConfigStatus(report.HasConfig), + requiredCount, + formatIssuesByCategory(report.Issues, "required"), + recommendedCount, + formatIssuesByCategory(report.Issues, "recommended"), + optionalCount, + formatIssuesByCategory(report.Issues, "optional"), + bestPracticeCount, + formatIssuesByCategory(report.Issues, "best_practice"), + len(report.MissingImports), + formatList(report.MissingImports), + ) + + // === RAG 增强:查询最佳实践和代码规范 === + var ragBestPractices string + + // 根据验证结果构建更针对性的查询 + queryBuilder := []string{"Higress WASM 插件"} + + // 根据发现的问题类型添加关键词 + if requiredCount > 0 || recommendedCount > 0 { + queryBuilder = append(queryBuilder, "常见错误") + + // 检查具体问题类型 + for _, issue := range report.Issues { + switch issue.Type { + case "error_handling": + queryBuilder = append(queryBuilder, "错误处理") + case "api_usage": + queryBuilder = append(queryBuilder, "API 使用规范") + case "config": + queryBuilder = append(queryBuilder, "配置解析") + case "logging": + queryBuilder = append(queryBuilder, "日志记录") + } + } + } else { + // 代码已通过基础验证,查询优化建议 + queryBuilder = append(queryBuilder, "性能优化 最佳实践") + } + + // 根据回调函数类型添加特定查询 + for _, callback := range report.FoundCallbacks { + if strings.Contains(callback, "RequestHeaders") { + queryBuilder = append(queryBuilder, "请求头处理") + } + if strings.Contains(callback, "RequestBody") { + queryBuilder = append(queryBuilder, "请求体处理") + } + if strings.Contains(callback, "ResponseHeaders") { + queryBuilder = append(queryBuilder, "响应头处理") + } + } + + // 如果有缺失的导入,查询包管理相关信息 + if len(report.MissingImports) > 0 { + queryBuilder = append(queryBuilder, "依赖包导入") + } + + queryString := strings.Join(queryBuilder, " ") + + // 只有当 RAG 启用时才查询 + if s.ragManager != nil && s.ragManager.IsEnabled() { + log.Printf("RAG Query: %s", queryString) + + ragContext, err := s.ragManager.QueryForTool( + "validate_wasm_code", + queryString, + "best_practice", + ) + + if err == nil && ragContext.Enabled && len(ragContext.Documents) > 0 { + log.Printf("RAG: Found %d best practice documents", len(ragContext.Documents)) + ragBestPractices = "\n\n### 最佳实践建议(来自知识库)\n\n" + ragContext.FormatContextForAI() + userMessage += ragBestPractices + } else { + if err != nil { + log.Printf(" RAG query failed for validation: %v", err) + } + } + } + + // 根据问题级别给出建议 + hasRequired := requiredCount > 0 + if hasRequired { + userMessage += "\n **请优先修复 \"必须修复\" 的问题**\n\n" + } else if recommendedCount > 0 { + userMessage += "\n **代码基本结构正确**,建议修复 \"建议修复\" 的问题\n\n" + } else { + userMessage += "\n **代码验证通过!** 可以调用 `generate_deployment_config` 生成部署配置\n\n" + } + + return tools.FormatToolResultWithAIContext(userMessage, "", report) +} + +// GenerateDeploymentConfig 生成部署配置 +func (s *MCPServer) GenerateDeploymentConfig(args map[string]interface{}) tools.ToolResult { + pluginName, ok := args["plugin_name"].(string) + if !ok { + return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing plugin_name"}}} + } + + goCode, ok := args["go_code"].(string) + if !ok { + return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing go_code"}}} + } + + namespace := "higress-system" + if ns, ok := args["namespace"].(string); ok && ns != "" { + namespace = ns + } + + configSchema := "" + if cs, ok := args["config_schema"].(string); ok { + configSchema = cs + } + + // 生成部署包 + pkg := tools.GenerateDeploymentPackage(pluginName, goCode, configSchema, namespace) + + // 格式化输出 + userMessage := fmt.Sprintf(`🎉 部署配置生成完成! + +插件 **%s** 的部署配置已生成(命名空间: %s) + +## 生成的文件 + +1. **wasmplugin.yaml** - WasmPlugin 配置 +2. **Makefile** - 构建和部署脚本 +3. **Dockerfile** - 容器化打包 +4. **README.md** - 使用文档 +5. **test.sh** - 测试脚本 + +## 快速部署 + +`+"```bash"+` +# 构建插件 +make build + +# 构建并推送镜像 +make docker-build docker-push + +# 部署 +make deploy + +# 验证 +kubectl get wasmplugin -n %s +`+"```"+` + +## 配置文件 + +### wasmplugin.yaml +`+"```yaml"+` +%s +`+"```"+` + +### Makefile +`+"```makefile"+` +%s +`+"```"+` + +### Dockerfile +`+"```dockerfile"+` +%s +`+"```"+` + +### README.md +`+"```markdown"+` +%s +`+"```"+` + +### test.sh +`+"```bash"+` +%s +`+"```"+` +`, + pluginName, + namespace, + namespace, + pkg.WasmPluginYAML, + pkg.Makefile, + pkg.Dockerfile, + pkg.README, + pkg.TestScript, + ) + + return tools.FormatToolResultWithAIContext(userMessage, "", pkg) +} + +// 辅助格式化函数 + +func formatWarningsListForUser(warnings []string) string { + if len(warnings) == 0 { + return "无" + } + return strings.Join(warnings, "\n- ") +} + +func formatCallbacksList(callbacks []string) string { + if len(callbacks) == 0 { + return "无" + } + return "- " + strings.Join(callbacks, "\n- ") +} + +func formatConfigStatus(hasConfig bool) string { + if hasConfig { + return " 已定义配置结构体" + } + return "- 未定义配置结构体(如不需要配置可忽略)" +} + +func formatIssuesByCategory(issues []tools.ValidationIssue, category string) string { + var filtered []string + for _, issue := range issues { + if issue.Category == category { + filtered = append(filtered, fmt.Sprintf("- **[%s]** %s\n 💡 建议: %s\n 📌 影响: %s", + issue.Type, issue.Message, issue.Suggestion, issue.Impact)) + } + } + if len(filtered) == 0 { + return "无" + } + return strings.Join(filtered, "\n\n") +} + +func formatList(items []string) string { + if len(items) == 0 { + return "无" + } + return "- " + strings.Join(items, "\n- ") +} + +// formatFeatures 格式化特性列表 +func formatFeatures(features map[string]bool) string { + featureNames := map[string]string{ + "ssl": "SSL/TLS 加密", + "proxy": "反向代理", + "rewrite": "URL 重写", + "redirect": "重定向", + "return": "返回指令", + "complex_routing": "复杂路由匹配", + "header_manipulation": "请求头操作", + "response_headers": "响应头操作", + } + + var result []string + for key, enabled := range features { + if enabled { + if name, ok := featureNames[key]; ok { + result = append(result, fmt.Sprintf("- %s", name)) + } else { + result = append(result, fmt.Sprintf("- %s", key)) + } + } + } + + if len(result) == 0 { + return "- 基础配置(无特殊特性)" + } + return strings.Join(result, "\n") +} + +// formatSuggestions 格式化建议列表 +func formatSuggestions(suggestions []string) string { + if len(suggestions) == 0 { + return "- 无特殊建议" + } + var result []string + for _, s := range suggestions { + result = append(result, fmt.Sprintf("- 💡 %s", s)) + } + return strings.Join(result, "\n") +} diff --git a/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/standalone/types.go b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/standalone/types.go new file mode 100644 index 000000000..1baa7c595 --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/standalone/types.go @@ -0,0 +1,34 @@ +// Common types for nginx migration MCP server - Standalone Mode +package standalone + +import ( + "nginx-migration-mcp/internal/rag" +) + +// MCPMessage represents a Model Context Protocol message structure +type MCPMessage struct { + JSONRPC string `json:"jsonrpc"` + Method string `json:"method,omitempty"` + Params interface{} `json:"params,omitempty"` + ID interface{} `json:"id,omitempty"` + Result interface{} `json:"result,omitempty"` + Error *MCPError `json:"error,omitempty"` +} + +type MCPError struct { + Code int `json:"code"` + Message string `json:"message"` +} + +type CallToolParams struct { + Name string `json:"name"` + Arguments map[string]interface{} `json:"arguments,omitempty"` +} + +type MCPServer struct { + config *ServerConfig + ragManager *rag.RAGManager +} + +// MCPServer implements the tools.MCPServer interface +// Method implementations are in server.go diff --git a/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/tools/lua_converter.go b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/tools/lua_converter.go new file mode 100644 index 000000000..b399aa3fc --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/tools/lua_converter.go @@ -0,0 +1,405 @@ +// Lua to WASM conversion logic for Nginx migration +package tools + +import ( + "fmt" + "regexp" + "strings" + "text/template" +) + +// LuaAnalyzer analyzes Lua script features and generates conversion mappings +type LuaAnalyzer struct { + Features map[string]bool + Variables map[string]string + Functions []LuaFunction + Warnings []string + Complexity string +} + +type LuaFunction struct { + Name string + Body string + Phase string // request_headers, request_body, response_headers, etc. +} + +// ConversionResult holds the generated WASM plugin code +type ConversionResult struct { + PluginName string + GoCode string + ConfigSchema string + Dependencies []string + WasmPluginYAML string +} + +// AnalyzeLuaScript performs detailed analysis of Lua script +func AnalyzeLuaScript(luaCode string) *LuaAnalyzer { + analyzer := &LuaAnalyzer{ + Features: make(map[string]bool), + Variables: make(map[string]string), + Functions: []LuaFunction{}, + Warnings: []string{}, + Complexity: "simple", + } + + lines := strings.Split(luaCode, "\n") + + for _, line := range lines { + line = strings.TrimSpace(line) + if line == "" || strings.HasPrefix(line, "--") { + continue + } + + // 分析ngx变量使用 + analyzer.analyzeNginxVars(line) + + // 分析API调用 + analyzer.analyzeAPICalls(line) + + // 分析函数定义 + analyzer.analyzeFunctions(line, luaCode) + } + + // 根据特性确定复杂度 + analyzer.determineComplexity() + + return analyzer +} + +func (la *LuaAnalyzer) analyzeNginxVars(line string) { + // 匹配 ngx.var.xxx 模式 + varPattern := regexp.MustCompile(`ngx\.var\.(\w+)`) + matches := varPattern.FindAllStringSubmatch(line, -1) + + for _, match := range matches { + if len(match) > 1 { + varName := match[1] + la.Features["ngx.var"] = true + + // 映射常见变量到WASM等价物 + switch varName { + case "uri": + la.Variables[varName] = "proxywasm.GetHttpRequestHeader(\":path\")" + case "request_method": + la.Variables[varName] = "proxywasm.GetHttpRequestHeader(\":method\")" + case "host": + la.Variables[varName] = "proxywasm.GetHttpRequestHeader(\":authority\")" + case "remote_addr": + la.Variables[varName] = "proxywasm.GetHttpRequestHeader(\"x-forwarded-for\")" + case "request_uri": + la.Variables[varName] = "proxywasm.GetHttpRequestHeader(\":path\")" + case "scheme": + la.Variables[varName] = "proxywasm.GetHttpRequestHeader(\":scheme\")" + default: + la.Variables[varName] = fmt.Sprintf("proxywasm.GetHttpRequestHeader(\"%s\")", varName) + } + } + } +} + +func (la *LuaAnalyzer) analyzeAPICalls(line string) { + apiCalls := map[string]string{ + "ngx.req.get_headers": "request_headers", + "ngx.req.get_body_data": "request_body", + "ngx.req.read_body": "request_body", + "ngx.exit": "response_control", + "ngx.say": "response_control", + "ngx.print": "response_control", + "ngx.shared": "shared_dict", + "ngx.location.capture": "internal_request", + "ngx.req.set_header": "header_manipulation", + "ngx.header": "response_headers", + } + + for apiCall, feature := range apiCalls { + if strings.Contains(line, apiCall) { + la.Features[feature] = true + + // 添加特定警告 + switch feature { + case "shared_dict": + la.Warnings = append(la.Warnings, "共享字典需要使用Redis或其他外部缓存替代") + case "internal_request": + la.Warnings = append(la.Warnings, "内部请求需要改为HTTP客户端调用") + } + } + } +} + +func (la *LuaAnalyzer) analyzeFunctions(line string, fullCode string) { + // 检测函数定义 + funcPattern := regexp.MustCompile(`function\s+(\w+)\s*\(`) + matches := funcPattern.FindAllStringSubmatch(line, -1) + + for _, match := range matches { + if len(match) > 1 { + funcName := match[1] + + // 提取函数体 (简化实现) + funcBody := la.extractFunctionBody(fullCode, funcName) + + // 根据函数名推断执行阶段 + phase := "request_headers" + if strings.Contains(funcName, "body") { + phase = "request_body" + } else if strings.Contains(funcName, "response") { + phase = "response_headers" + } + + la.Functions = append(la.Functions, LuaFunction{ + Name: funcName, + Body: funcBody, + Phase: phase, + }) + } + } +} + +func (la *LuaAnalyzer) extractFunctionBody(fullCode, funcName string) string { + // 简化的函数体提取 - 实际实现应该更复杂 + pattern := fmt.Sprintf(`function\s+%s\s*\([^)]*\)(.*?)end`, funcName) + re := regexp.MustCompile(pattern) + match := re.FindStringSubmatch(fullCode) + if len(match) > 1 { + return strings.TrimSpace(match[1]) + } + return "" +} + +func (la *LuaAnalyzer) determineComplexity() { + warningCount := len(la.Warnings) + featureCount := len(la.Features) + + if warningCount > 3 || featureCount > 6 { + la.Complexity = "complex" + } else if warningCount > 1 || featureCount > 3 { + la.Complexity = "medium" + } +} + +// ConvertLuaToWasm converts analyzed Lua script to WASM plugin +func ConvertLuaToWasm(analyzer *LuaAnalyzer, pluginName string) (*ConversionResult, error) { + result := &ConversionResult{ + PluginName: pluginName, + Dependencies: []string{ + "github.com/higress-group/proxy-wasm-go-sdk/proxywasm", + "github.com/higress-group/wasm-go/pkg/wrapper", + "github.com/higress-group/wasm-go/pkg/log", + }, + } + + // 生成Go代码 + goCode, err := generateGoCode(analyzer, pluginName) + if err != nil { + return nil, err + } + result.GoCode = goCode + + // 生成配置模式 + result.ConfigSchema = generateConfigSchema(analyzer) + + // 生成WasmPlugin YAML + result.WasmPluginYAML = generateWasmPluginYAML(pluginName) + + return result, nil +} + +func generateGoCode(analyzer *LuaAnalyzer, pluginName string) (string, error) { + tmpl := `// Generated WASM plugin from Lua script +// Plugin: {{.PluginName}} +package main + +import ( + "net/http" + "strings" + + "github.com/higress-group/proxy-wasm-go-sdk/proxywasm" + "github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types" + "github.com/higress-group/wasm-go/pkg/log" + "github.com/higress-group/wasm-go/pkg/wrapper" + "github.com/tidwall/gjson" +) + +func main() {} + +func init() { + wrapper.SetCtx( + "{{.PluginName}}", + wrapper.ParseConfigBy(parseConfig), + {{- if .HasRequestHeaders}} + wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders), + {{- end}} + {{- if .HasRequestBody}} + wrapper.ProcessRequestBodyBy(onHttpRequestBody), + {{- end}} + {{- if .HasResponseHeaders}} + wrapper.ProcessResponseHeadersBy(onHttpResponseHeaders), + {{- end}} + ) +} + +type {{.ConfigTypeName}} struct { + // Generated from Lua analysis + {{- range .ConfigFields}} + {{.Name}} {{.Type}} ` + "`json:\"{{.JSONName}}\"`" + ` + {{- end}} +} + +func parseConfig(json gjson.Result, config *{{.ConfigTypeName}}, log log.Log) error { + {{- range .ConfigFields}} + config.{{.Name}} = json.Get("{{.JSONName}}").{{.ParseMethod}}() + {{- end}} + return nil +} + +{{- if .HasRequestHeaders}} +func onHttpRequestHeaders(ctx wrapper.HttpContext, config {{.ConfigTypeName}}, log log.Log) types.Action { + {{.RequestHeadersLogic}} + return types.ActionContinue +} +{{- end}} + +{{- if .HasRequestBody}} +func onHttpRequestBody(ctx wrapper.HttpContext, config {{.ConfigTypeName}}, body []byte, log log.Log) types.Action { + {{.RequestBodyLogic}} + return types.ActionContinue +} +{{- end}} + +{{- if .HasResponseHeaders}} +func onHttpResponseHeaders(ctx wrapper.HttpContext, config {{.ConfigTypeName}}, log log.Log) types.Action { + {{.ResponseHeadersLogic}} + return types.ActionContinue +} +{{- end}} +` + + // 准备模板数据 + data := map[string]interface{}{ + "PluginName": pluginName, + "ConfigTypeName": strings.Title(pluginName) + "Config", + "HasRequestHeaders": analyzer.Features["request_headers"] || analyzer.Features["ngx.var"], + "HasRequestBody": analyzer.Features["request_body"], + "HasResponseHeaders": analyzer.Features["response_headers"] || analyzer.Features["response_control"], + "ConfigFields": generateConfigFields(analyzer), + "RequestHeadersLogic": generateRequestHeadersLogic(analyzer), + "RequestBodyLogic": generateRequestBodyLogic(analyzer), + "ResponseHeadersLogic": generateResponseHeadersLogic(analyzer), + } + + t, err := template.New("wasm").Parse(tmpl) + if err != nil { + return "", err + } + + var buf strings.Builder + err = t.Execute(&buf, data) + if err != nil { + return "", err + } + + return buf.String(), nil +} + +func generateConfigFields(analyzer *LuaAnalyzer) []map[string]string { + fields := []map[string]string{} + + // 基于分析的特性生成配置字段 + if analyzer.Features["response_control"] { + fields = append(fields, map[string]string{ + "Name": "EnableCustomResponse", + "Type": "bool", + "JSONName": "enable_custom_response", + "ParseMethod": "Bool", + }) + } + + return fields +} + +func generateRequestHeadersLogic(analyzer *LuaAnalyzer) string { + logic := []string{} + + // 基于变量使用生成逻辑 + for varName, wasmCall := range analyzer.Variables { + logic = append(logic, fmt.Sprintf(` + // Access to ngx.var.%s + %s, err := %s + if err != nil { + log.Warnf("Failed to get %s: %%v", err) + }`, varName, varName, wasmCall, varName)) + } + + if analyzer.Features["header_manipulation"] { + logic = append(logic, ` + // Header manipulation logic + err := proxywasm.AddHttpRequestHeader("x-converted-from", "nginx-lua") + if err != nil { + log.Warnf("Failed to add header: %v", err) + }`) + } + + return strings.Join(logic, "\n") +} + +func generateRequestBodyLogic(analyzer *LuaAnalyzer) string { + if analyzer.Features["request_body"] { + return ` + // Process request body + bodyStr := string(body) + log.Infof("Processing request body: %s", bodyStr) + + // Add your body processing logic here + ` + } + return "// No request body processing needed" +} + +func generateResponseHeadersLogic(analyzer *LuaAnalyzer) string { + if analyzer.Features["response_control"] { + return ` + // Response control logic + if config.EnableCustomResponse { + proxywasm.SendHttpResponseWithDetail(200, "lua-converted", nil, []byte("Response from converted Lua plugin"), -1) + return types.ActionContinue + } + ` + } + return "// No response processing needed" +} + +func generateConfigSchema(analyzer *LuaAnalyzer) string { + schema := `{ + "type": "object", + "properties": {` + + properties := []string{} + + if analyzer.Features["response_control"] { + properties = append(properties, ` + "enable_custom_response": { + "type": "boolean", + "description": "Enable custom response handling" + }`) + } + + schema += strings.Join(properties, ",") + schema += ` + } +}` + + return schema +} + +func generateWasmPluginYAML(pluginName string) string { + return fmt.Sprintf(`apiVersion: extensions.higress.io/v1alpha1 +kind: WasmPlugin +metadata: + name: %s + namespace: higress-system +spec: + defaultConfig: + enable_custom_response: true + url: oci://your-registry/%s:latest +`, pluginName, pluginName) +} diff --git a/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/tools/mcp_tools.go b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/tools/mcp_tools.go new file mode 100644 index 000000000..dde52e40f --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/tools/mcp_tools.go @@ -0,0 +1,223 @@ +// MCP Tools Definitions +// 定义所有可用的MCP工具及其描述信息 +package tools + +import ( + "encoding/json" + "os" +) + +// MCPTool represents a tool definition in MCP protocol +type MCPTool struct { + Name string `json:"name"` + Description string `json:"description"` + InputSchema json.RawMessage `json:"inputSchema"` +} + +// ToolResult represents the result of a tool call +type ToolResult struct { + Content []Content `json:"content"` +} + +// Content represents content within a tool result +type Content struct { + Type string `json:"type"` + Text string `json:"text"` +} + +// MCPServer is an interface for server methods needed by tool handlers +type MCPServer interface { + ParseNginxConfig(args map[string]interface{}) ToolResult + ConvertToHigress(args map[string]interface{}) ToolResult + AnalyzeLuaPlugin(args map[string]interface{}) ToolResult + ConvertLuaToWasm(args map[string]interface{}) ToolResult + // 新增工具链方法 + GenerateConversionHints(args map[string]interface{}) ToolResult + ValidateWasmCode(args map[string]interface{}) ToolResult + GenerateDeploymentConfig(args map[string]interface{}) ToolResult +} + +// MCPToolsConfig 工具配置文件结构 +type MCPToolsConfig struct { + Version string `json:"version"` + Name string `json:"name"` + Description string `json:"description"` + Tools []MCPTool `json:"tools"` +} + +// LoadToolsFromFile 从 JSON 文件加载工具定义 +func LoadToolsFromFile(filename string) ([]MCPTool, error) { + data, err := os.ReadFile(filename) + if err != nil { + // 文件不存在时使用默认配置 + return GetMCPToolsDefault(), nil + } + + var config MCPToolsConfig + if err := json.Unmarshal(data, &config); err != nil { + return nil, err + } + + return config.Tools, nil +} + +// GetMCPTools 返回所有可用的 MCP 工具定义 +// 优先从 mcp-tools.json 加载,失败时使用默认定义 +func GetMCPTools() []MCPTool { + tools, err := LoadToolsFromFile("mcp-tools.json") + if err != nil { + return GetMCPToolsDefault() + } + return tools +} + +// GetMCPToolsDefault 返回默认的工具定义(包含完整工具链) +func GetMCPToolsDefault() []MCPTool { + return []MCPTool{ + { + Name: "parse_nginx_config", + Description: "解析和分析 Nginx 配置文件,识别配置结构和复杂度", + InputSchema: json.RawMessage(`{ + "type": "object", + "properties": { + "config_content": { + "type": "string", + "description": "Nginx 配置文件内容" + } + }, + "required": ["config_content"] + }`), + }, + { + Name: "convert_to_higress", + Description: "将 Nginx 配置转换为 Higress HTTPRoute 和 Service 资源", + InputSchema: json.RawMessage(`{ + "type": "object", + "properties": { + "config_content": { + "type": "string", + "description": "Nginx 配置文件内容" + }, + "namespace": { + "type": "string", + "description": "目标 Kubernetes 命名空间", + "default": "default" + } + }, + "required": ["config_content"] + }`), + }, + { + Name: "analyze_lua_plugin", + Description: "分析 Nginx Lua 插件的兼容性,识别使用的 API 和潜在迁移问题,返回结构化分析结果", + InputSchema: json.RawMessage(`{ + "type": "object", + "properties": { + "lua_code": { + "type": "string", + "description": "Nginx Lua 插件代码" + } + }, + "required": ["lua_code"] + }`), + }, + { + Name: "generate_conversion_hints", + Description: "基于 Lua 分析结果生成代码转换模板", + InputSchema: json.RawMessage(`{ + "type": "object", + "properties": { + "analysis_result": { + "type": "string", + "description": "analyze_lua_plugin 返回的 JSON 格式分析结果" + }, + "plugin_name": { + "type": "string", + "description": "目标插件名称(小写字母和连字符)" + } + }, + "required": ["analysis_result", "plugin_name"] + }`), + }, + { + Name: "validate_wasm_code", + Description: "验证生成的 Go WASM 插件代码,检查语法、API 使用、配置结构等,输出验证报告和改进建议", + InputSchema: json.RawMessage(`{ + "type": "object", + "properties": { + "go_code": { + "type": "string", + "description": "生成的 Go WASM 插件代码" + }, + "plugin_name": { + "type": "string", + "description": "插件名称" + } + }, + "required": ["go_code", "plugin_name"] + }`), + }, + { + Name: "generate_deployment_config", + Description: "为验证通过的 WASM 插件生成完整的部署配置包,包括 WasmPlugin YAML、Makefile、Dockerfile、README 和测试脚本", + InputSchema: json.RawMessage(`{ + "type": "object", + "properties": { + "plugin_name": { + "type": "string", + "description": "插件名称" + }, + "go_code": { + "type": "string", + "description": "验证通过的 Go 代码" + }, + "config_schema": { + "type": "string", + "description": "配置 JSON Schema(可选)" + }, + "namespace": { + "type": "string", + "description": "部署命名空间", + "default": "higress-system" + } + }, + "required": ["plugin_name", "go_code"] + }`), + }, + { + Name: "convert_lua_to_wasm", + Description: "一键将 Nginx Lua 脚本转换为 Higress WASM 插件,自动生成 Go 代码和 WasmPlugin 配置。适合简单插件快速转换", + InputSchema: json.RawMessage(`{ + "type": "object", + "properties": { + "lua_code": { + "type": "string", + "description": "要转换的 Nginx Lua 插件代码" + }, + "plugin_name": { + "type": "string", + "description": "生成的 WASM 插件名称 (小写字母和连字符)" + } + }, + "required": ["lua_code", "plugin_name"] + }`), + }, + } +} + +// ToolHandler 定义工具处理函数的类型 +type ToolHandler func(args map[string]interface{}) ToolResult + +// GetToolHandlers 返回工具名称到处理函数的映射 +func GetToolHandlers(s MCPServer) map[string]ToolHandler { + return map[string]ToolHandler{ + "parse_nginx_config": s.ParseNginxConfig, + "convert_to_higress": s.ConvertToHigress, + "analyze_lua_plugin": s.AnalyzeLuaPlugin, + "convert_lua_to_wasm": s.ConvertLuaToWasm, + // 新增工具链处理器 + "generate_conversion_hints": s.GenerateConversionHints, + "validate_wasm_code": s.ValidateWasmCode, + "generate_deployment_config": s.GenerateDeploymentConfig, + } +} diff --git a/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/tools/nginx_parser.go b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/tools/nginx_parser.go new file mode 100644 index 000000000..1ece0e242 --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/tools/nginx_parser.go @@ -0,0 +1,426 @@ +// Package tools provides Nginx configuration parsing and analysis capabilities. +// This intelligent parser extracts semantic information from Nginx configs for AI reasoning. +package tools + +import ( + "fmt" + "regexp" + "strings" +) + +// NginxConfig 表示解析后的 Nginx 配置结构 +type NginxConfig struct { + Servers []NginxServer `json:"servers"` + Upstreams []NginxUpstream `json:"upstreams"` + Raw string `json:"raw"` +} + +// NginxServer 表示一个 server 块 +type NginxServer struct { + Listen []string `json:"listen"` // 监听端口和地址 + ServerNames []string `json:"server_names"` // 域名列表 + Locations []NginxLocation `json:"locations"` // location 块列表 + SSL *NginxSSL `json:"ssl,omitempty"` // SSL 配置 + Directives map[string][]string `json:"directives"` // 其他指令 +} + +// NginxLocation 表示一个 location 块 +type NginxLocation struct { + Path string `json:"path"` // 路径 + Modifier string `json:"modifier"` // 修饰符(=, ~, ~*, ^~) + ProxyPass string `json:"proxy_pass,omitempty"` // 代理目标 + Rewrite []string `json:"rewrite,omitempty"` // rewrite 规则 + Return *NginxReturn `json:"return,omitempty"` // return 指令 + Directives map[string][]string `json:"directives"` // 其他指令 +} + +// NginxSSL 表示 SSL 配置 +type NginxSSL struct { + Certificate string `json:"certificate,omitempty"` + CertificateKey string `json:"certificate_key,omitempty"` + Protocols []string `json:"protocols,omitempty"` + Ciphers string `json:"ciphers,omitempty"` +} + +// NginxReturn 表示 return 指令 +type NginxReturn struct { + Code int `json:"code"` + URL string `json:"url,omitempty"` + Text string `json:"text,omitempty"` +} + +// NginxUpstream 表示 upstream 块 +type NginxUpstream struct { + Name string `json:"name"` + Servers []string `json:"servers"` + Method string `json:"method,omitempty"` // 负载均衡方法 +} + +// ParseNginxConfig 解析 Nginx 配置内容 +func ParseNginxConfig(content string) (*NginxConfig, error) { + config := &NginxConfig{ + Raw: content, + Servers: []NginxServer{}, + Upstreams: []NginxUpstream{}, + } + + // 解析 upstream 块 + upstreams := extractUpstreams(content) + config.Upstreams = upstreams + + // 解析 server 块 + servers := extractServers(content) + for _, serverContent := range servers { + server := parseServer(serverContent) + config.Servers = append(config.Servers, server) + } + + return config, nil +} + +// extractUpstreams 提取所有 upstream 块 +func extractUpstreams(content string) []NginxUpstream { + upstreams := []NginxUpstream{} + upstreamRegex := regexp.MustCompile(`upstream\s+(\S+)\s*\{([^}]*)\}`) + matches := upstreamRegex.FindAllStringSubmatch(content, -1) + + for _, match := range matches { + if len(match) >= 3 { + name := match[1] + body := match[2] + upstream := NginxUpstream{ + Name: name, + Servers: []string{}, + } + + // 提取 server 指令 + serverRegex := regexp.MustCompile(`server\s+([^;]+);`) + serverMatches := serverRegex.FindAllStringSubmatch(body, -1) + for _, sm := range serverMatches { + if len(sm) >= 2 { + upstream.Servers = append(upstream.Servers, strings.TrimSpace(sm[1])) + } + } + + // 检测负载均衡方法 + if strings.Contains(body, "ip_hash") { + upstream.Method = "ip_hash" + } else if strings.Contains(body, "least_conn") { + upstream.Method = "least_conn" + } + + upstreams = append(upstreams, upstream) + } + } + + return upstreams +} + +// extractServers 提取所有 server 块的内容 +func extractServers(content string) []string { + servers := []string{} + + // 简单的大括号匹配提取 + lines := strings.Split(content, "\n") + inServer := false + braceCount := 0 + var currentServer strings.Builder + + for _, line := range lines { + trimmed := strings.TrimSpace(line) + + // 检测 server 块开始 + if strings.HasPrefix(trimmed, "server") && strings.Contains(trimmed, "{") { + inServer = true + currentServer.Reset() + currentServer.WriteString(line + "\n") + braceCount = strings.Count(line, "{") - strings.Count(line, "}") + continue + } + + if inServer { + currentServer.WriteString(line + "\n") + braceCount += strings.Count(line, "{") - strings.Count(line, "}") + + if braceCount == 0 { + servers = append(servers, currentServer.String()) + inServer = false + } + } + } + + return servers +} + +// parseServer 解析单个 server 块 +func parseServer(content string) NginxServer { + server := NginxServer{ + Listen: []string{}, + ServerNames: []string{}, + Locations: []NginxLocation{}, + Directives: make(map[string][]string), + } + + lines := strings.Split(content, "\n") + + // 解析 listen 指令 + listenRegex := regexp.MustCompile(`^\s*listen\s+([^;]+);`) + for _, line := range lines { + if match := listenRegex.FindStringSubmatch(line); match != nil { + server.Listen = append(server.Listen, strings.TrimSpace(match[1])) + } + } + + // 解析 server_name 指令 + serverNameRegex := regexp.MustCompile(`^\s*server_name\s+([^;]+);`) + for _, line := range lines { + if match := serverNameRegex.FindStringSubmatch(line); match != nil { + names := strings.Fields(match[1]) + server.ServerNames = append(server.ServerNames, names...) + } + } + + // 解析 SSL 配置 + server.SSL = parseSSL(content) + + // 解析 location 块 + server.Locations = extractLocations(content) + + // 解析其他常见指令 + commonDirectives := []string{ + "root", "index", "access_log", "error_log", + "client_max_body_size", "proxy_set_header", + } + + for _, directive := range commonDirectives { + pattern := fmt.Sprintf(`(?m)^\s*%s\s+([^;]+);`, directive) + regex := regexp.MustCompile(pattern) + matches := regex.FindAllStringSubmatch(content, -1) + for _, match := range matches { + if len(match) >= 2 { + server.Directives[directive] = append(server.Directives[directive], strings.TrimSpace(match[1])) + } + } + } + + return server +} + +// parseSSL 解析 SSL 配置 +func parseSSL(content string) *NginxSSL { + hasSSL := strings.Contains(content, "ssl") || strings.Contains(content, "443") + if !hasSSL { + return nil + } + + ssl := &NginxSSL{ + Protocols: []string{}, + } + + // 提取证书路径 + certRegex := regexp.MustCompile(`ssl_certificate\s+([^;]+);`) + if match := certRegex.FindStringSubmatch(content); match != nil { + ssl.Certificate = strings.TrimSpace(match[1]) + } + + // 提取私钥路径 + keyRegex := regexp.MustCompile(`ssl_certificate_key\s+([^;]+);`) + if match := keyRegex.FindStringSubmatch(content); match != nil { + ssl.CertificateKey = strings.TrimSpace(match[1]) + } + + // 提取协议 + protocolRegex := regexp.MustCompile(`ssl_protocols\s+([^;]+);`) + if match := protocolRegex.FindStringSubmatch(content); match != nil { + ssl.Protocols = strings.Fields(match[1]) + } + + // 提取加密套件 + cipherRegex := regexp.MustCompile(`ssl_ciphers\s+([^;]+);`) + if match := cipherRegex.FindStringSubmatch(content); match != nil { + ssl.Ciphers = strings.TrimSpace(match[1]) + } + + return ssl +} + +// extractLocations 提取所有 location 块 +func extractLocations(content string) []NginxLocation { + locations := []NginxLocation{} + + // 匹配 location 块 + locationRegex := regexp.MustCompile(`location\s+(=|~|~\*|\^~)?\s*([^\s{]+)\s*\{([^}]*)\}`) + matches := locationRegex.FindAllStringSubmatch(content, -1) + + for _, match := range matches { + if len(match) >= 4 { + modifier := strings.TrimSpace(match[1]) + path := strings.TrimSpace(match[2]) + body := match[3] + + location := NginxLocation{ + Path: path, + Modifier: modifier, + Rewrite: []string{}, + Directives: make(map[string][]string), + } + + // 提取 proxy_pass + proxyPassRegex := regexp.MustCompile(`proxy_pass\s+([^;]+);`) + if ppMatch := proxyPassRegex.FindStringSubmatch(body); ppMatch != nil { + location.ProxyPass = strings.TrimSpace(ppMatch[1]) + } + + // 提取 rewrite 规则 + rewriteRegex := regexp.MustCompile(`rewrite\s+([^;]+);`) + rewriteMatches := rewriteRegex.FindAllStringSubmatch(body, -1) + for _, rm := range rewriteMatches { + if len(rm) >= 2 { + location.Rewrite = append(location.Rewrite, strings.TrimSpace(rm[1])) + } + } + + // 提取 return 指令 + returnRegex := regexp.MustCompile(`return\s+(\d+)(?:\s+([^;]+))?;`) + if retMatch := returnRegex.FindStringSubmatch(body); retMatch != nil { + code := 0 + fmt.Sscanf(retMatch[1], "%d", &code) + location.Return = &NginxReturn{ + Code: code, + } + if len(retMatch) >= 3 { + urlOrText := strings.TrimSpace(retMatch[2]) + if strings.HasPrefix(urlOrText, "http") { + location.Return.URL = urlOrText + } else { + location.Return.Text = urlOrText + } + } + } + + // 提取其他指令 + commonDirectives := []string{ + "proxy_set_header", "proxy_redirect", "proxy_read_timeout", + "add_header", "alias", "root", "try_files", + } + + for _, directive := range commonDirectives { + pattern := fmt.Sprintf(`(?m)^\s*%s\s+([^;]+);`, directive) + regex := regexp.MustCompile(pattern) + matches := regex.FindAllStringSubmatch(body, -1) + for _, m := range matches { + if len(m) >= 2 { + location.Directives[directive] = append(location.Directives[directive], strings.TrimSpace(m[1])) + } + } + } + + locations = append(locations, location) + } + } + + return locations +} + +// AnalyzeNginxConfig 分析 Nginx 配置,生成用于 AI 的分析报告 +func AnalyzeNginxConfig(config *NginxConfig) *NginxAnalysis { + analysis := &NginxAnalysis{ + ServerCount: len(config.Servers), + Features: make(map[string]bool), + Complexity: "simple", + Suggestions: []string{}, + } + + totalLocations := 0 + hasSSL := false + hasRewrite := false + hasUpstream := len(config.Upstreams) > 0 + hasComplexRouting := false + uniqueDomains := make(map[string]bool) + + for _, server := range config.Servers { + // 统计域名 + for _, name := range server.ServerNames { + uniqueDomains[name] = true + } + + // 统计 location + totalLocations += len(server.Locations) + + // 检测 SSL + if server.SSL != nil { + hasSSL = true + analysis.Features["ssl"] = true + } + + // 检测 location 特性 + for _, loc := range server.Locations { + if loc.ProxyPass != "" { + analysis.Features["proxy"] = true + } + if len(loc.Rewrite) > 0 { + hasRewrite = true + analysis.Features["rewrite"] = true + } + if loc.Return != nil { + analysis.Features["return"] = true + if loc.Return.Code >= 300 && loc.Return.Code < 400 { + analysis.Features["redirect"] = true + } + } + if loc.Modifier != "" { + hasComplexRouting = true + analysis.Features["complex_routing"] = true + } + // 检测其他指令 + if _, ok := loc.Directives["proxy_set_header"]; ok { + analysis.Features["header_manipulation"] = true + } + if _, ok := loc.Directives["add_header"]; ok { + analysis.Features["response_headers"] = true + } + } + } + + analysis.LocationCount = totalLocations + analysis.DomainCount = len(uniqueDomains) + + // 判断复杂度 + if analysis.ServerCount > 3 || totalLocations > 10 || (hasRewrite && hasSSL && hasComplexRouting) { + analysis.Complexity = "high" + } else if analysis.ServerCount > 1 || totalLocations > 5 || hasRewrite || hasSSL || hasUpstream { + analysis.Complexity = "medium" + } + + // 生成建议 + if analysis.Features["proxy"] { + analysis.Suggestions = append(analysis.Suggestions, "proxy_pass 将转换为 Ingress/HTTPRoute 的 backend 配置") + } + if analysis.Features["rewrite"] { + analysis.Suggestions = append(analysis.Suggestions, "rewrite 规则需要使用 Higress 注解实现,如 higress.io/rewrite-target") + } + if analysis.Features["ssl"] { + analysis.Suggestions = append(analysis.Suggestions, "SSL 证书需要创建 Kubernetes Secret,并在 Ingress 中引用") + } + if analysis.Features["redirect"] { + analysis.Suggestions = append(analysis.Suggestions, "redirect 可以使用 Higress 的重定向注解或插件实现") + } + if hasUpstream { + analysis.Suggestions = append(analysis.Suggestions, "upstream 负载均衡将由 Kubernetes Service 和 Endpoints 实现") + } + if analysis.Features["header_manipulation"] { + analysis.Suggestions = append(analysis.Suggestions, "请求头操作可以使用 Higress 注解或 custom-response 插件实现") + } + + return analysis +} + +// NginxAnalysis 表示 Nginx 配置分析结果 +type NginxAnalysis struct { + ServerCount int `json:"server_count"` + LocationCount int `json:"location_count"` + DomainCount int `json:"domain_count"` + Features map[string]bool `json:"features"` + Complexity string `json:"complexity"` // simple, medium, high + Suggestions []string `json:"suggestions"` +} diff --git a/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/tools/tool_chain.go b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/tools/tool_chain.go new file mode 100644 index 000000000..b9392c267 --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/higress/nginx-migration/tools/tool_chain.go @@ -0,0 +1,448 @@ +// Tool Chain implementations for LLM-guided Lua to WASM conversion +package tools + +import ( + "encoding/json" + "fmt" + "regexp" + "strings" +) + +// AnalysisResultForAI 结构化的分析结果,用于 AI 协作 +type AnalysisResultForAI struct { + Features map[string]bool `json:"features"` + Variables map[string]string `json:"variables"` + APICalls []string `json:"api_calls"` + Warnings []string `json:"warnings"` + Complexity string `json:"complexity"` + Compatibility string `json:"compatibility"` + // OriginalCode 字段已移除,避免返回大量数据 +} + +// ConversionHints 代码转换提示(简化版) +type ConversionHints struct { + CodeTemplate string `json:"code_template"` + Warnings []string `json:"warnings"` +} + +// ValidationReport 验证报告 +type ValidationReport struct { + Issues []ValidationIssue `json:"issues"` // 所有发现的问题 + MissingImports []string `json:"missing_imports"` // 缺失的 import + FoundCallbacks []string `json:"found_callbacks"` // 找到的回调函数 + HasConfig bool `json:"has_config"` // 是否有配置结构 + Summary string `json:"summary"` // 总体评估摘要 +} + +// ValidationIssue 验证问题 +type ValidationIssue struct { + Category string `json:"category"` // required, recommended, optional, best_practice + Type string `json:"type"` // syntax, api_usage, config, error_handling, logging, etc. + Message string `json:"message"` // 问题描述 + Suggestion string `json:"suggestion"` // 改进建议 + Impact string `json:"impact"` // 影响说明(为什么重要) +} + +// DeploymentPackage 部署配置包 +type DeploymentPackage struct { + WasmPluginYAML string `json:"wasm_plugin_yaml"` + Makefile string `json:"makefile"` + Dockerfile string `json:"dockerfile"` + ConfigMap string `json:"config_map"` + README string `json:"readme"` + TestScript string `json:"test_script"` + Dependencies map[string]string `json:"dependencies"` +} + +// AnalyzeLuaPluginForAI 分析 Lua 插件并生成 AI 友好的输出 +func AnalyzeLuaPluginForAI(luaCode string) AnalysisResultForAI { + analyzer := AnalyzeLuaScript(luaCode) + + // 收集所有 API 调用 + apiCalls := []string{} + for feature := range analyzer.Features { + apiCalls = append(apiCalls, feature) + } + + // 确定兼容性级别 + compatibility := "full" + if len(analyzer.Warnings) > 0 { + compatibility = "partial" + } + if len(analyzer.Warnings) > 2 { + compatibility = "manual" + } + + return AnalysisResultForAI{ + Features: analyzer.Features, + Variables: analyzer.Variables, + APICalls: apiCalls, + Warnings: analyzer.Warnings, + Complexity: analyzer.Complexity, + Compatibility: compatibility, + } +} + +// GenerateConversionHints 生成代码转换提示(简化版) +func GenerateConversionHints(analysis AnalysisResultForAI, pluginName string) ConversionHints { + return ConversionHints{ + CodeTemplate: generateCodeTemplate(analysis, pluginName), + Warnings: analysis.Warnings, + } +} + +// generateCodeTemplate 生成代码模板提示 +func generateCodeTemplate(analysis AnalysisResultForAI, pluginName string) string { + callbacks := generateCallbackSummary(analysis) + return fmt.Sprintf(`生成 Go WASM 插件 %s,实现回调: %s +参考文档: https://higress.cn/docs/latest/user/wasm-go/`, + pluginName, callbacks) +} + +// generateCallbackRegistrations 生成回调注册代码 +func generateCallbackRegistrations(analysis AnalysisResultForAI) string { + callbacks := []string{} + + if analysis.Features["ngx.var"] || analysis.Features["request_headers"] || analysis.Features["header_manipulation"] { + callbacks = append(callbacks, "wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders)") + } + + if analysis.Features["request_body"] { + callbacks = append(callbacks, "wrapper.ProcessRequestBodyBy(onHttpRequestBody)") + } + + if analysis.Features["response_headers"] || analysis.Features["response_control"] { + callbacks = append(callbacks, "wrapper.ProcessResponseHeadersBy(onHttpResponseHeaders)") + } + + if len(callbacks) == 0 { + callbacks = append(callbacks, "wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders)") + } + + return "\n\t\t" + strings.Join(callbacks, ",\n\t\t") +} + +// generateCallbackSummary 生成回调函数摘要 +func generateCallbackSummary(analysis AnalysisResultForAI) string { + callbacks := []string{} + + if analysis.Features["ngx.var"] || analysis.Features["request_headers"] || analysis.Features["header_manipulation"] { + callbacks = append(callbacks, "onHttpRequestHeaders") + } + if analysis.Features["request_body"] { + callbacks = append(callbacks, "onHttpRequestBody") + } + if analysis.Features["response_headers"] || analysis.Features["response_control"] { + callbacks = append(callbacks, "onHttpResponseHeaders") + } + + if len(callbacks) == 0 { + return "onHttpRequestHeaders" + } + return strings.Join(callbacks, ", ") +} + +// ValidateWasmCode 验证生成的 Go WASM 代码 +func ValidateWasmCode(goCode, pluginName string) ValidationReport { + report := ValidationReport{ + Issues: []ValidationIssue{}, + MissingImports: []string{}, + FoundCallbacks: []string{}, + HasConfig: false, + } + + // 移除注释以避免误判 + codeWithoutComments := removeComments(goCode) + + // 检查必要的包声明 + packagePattern := regexp.MustCompile(`(?m)^package\s+main\s*$`) + if !packagePattern.MatchString(goCode) { + report.Issues = append(report.Issues, ValidationIssue{ + Category: "required", + Type: "syntax", + Message: "缺少 'package main' 声明", + Suggestion: "在文件开头添加: package main", + Impact: "WASM 插件必须使用 package main,否则无法编译", + }) + } + + // 检查 main 函数 + mainFuncPattern := regexp.MustCompile(`func\s+main\s*\(\s*\)`) + if !mainFuncPattern.MatchString(codeWithoutComments) { + report.Issues = append(report.Issues, ValidationIssue{ + Category: "required", + Type: "syntax", + Message: "缺少 main() 函数", + Suggestion: "添加空的 main 函数: func main() {}", + Impact: "WASM 插件必须有 main 函数,即使是空的", + }) + } + + // 检查 init 函数 + initFuncPattern := regexp.MustCompile(`func\s+init\s*\(\s*\)`) + if !initFuncPattern.MatchString(codeWithoutComments) { + report.Issues = append(report.Issues, ValidationIssue{ + Category: "required", + Type: "api_usage", + Message: "缺少 init() 函数", + Suggestion: "添加 init() 函数用于注册插件", + Impact: "插件需要在 init() 中调用 wrapper.SetCtx 进行注册", + }) + } + + // 检查 wrapper.SetCtx 调用 + setCtxPattern := regexp.MustCompile(`wrapper\.SetCtx\s*\(`) + if !setCtxPattern.MatchString(codeWithoutComments) { + report.Issues = append(report.Issues, ValidationIssue{ + Category: "required", + Type: "api_usage", + Message: "缺少 wrapper.SetCtx 调用", + Suggestion: "在 init() 函数中调用 wrapper.SetCtx 注册插件上下文", + Impact: "没有注册插件上下文将导致插件无法工作", + }) + } + + // 检查必要的 import + requiredImports := map[string]string{ + "github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types": "定义了 Action 等核心类型", + "github.com/higress-group/wasm-go/pkg/wrapper": "提供了 Higress 插件开发的高级封装", + } + + for importPath, reason := range requiredImports { + if !containsImport(goCode, importPath) { + report.MissingImports = append(report.MissingImports, importPath) + report.Issues = append(report.Issues, ValidationIssue{ + Category: "required", + Type: "imports", + Message: fmt.Sprintf("缺少必需的导入: %s", importPath), + Suggestion: fmt.Sprintf(`添加导入: import "%s"`, importPath), + Impact: reason, + }) + } + } + + // 检查可选但推荐的 import + if !containsImport(goCode, "github.com/higress-group/proxy-wasm-go-sdk/proxywasm") { + report.Issues = append(report.Issues, ValidationIssue{ + Category: "optional", + Type: "imports", + Message: "未导入 proxywasm 包", + Suggestion: "如需使用日志、HTTP 调用等底层 API,可导入 proxywasm 包", + Impact: "proxywasm 提供了日志记录、外部 HTTP 调用等功能", + }) + } + + // 检查配置结构体 + configPattern := regexp.MustCompile(`type\s+\w+Config\s+struct\s*\{`) + report.HasConfig = configPattern.MatchString(goCode) + + if !report.HasConfig { + report.Issues = append(report.Issues, ValidationIssue{ + Category: "optional", + Type: "config", + Message: "未定义配置结构体", + Suggestion: "如果插件需要配置参数,建议定义配置结构体(如 type MyPluginConfig struct { ... })", + Impact: "配置结构体用于接收和解析插件的配置参数,支持动态配置", + }) + } + + // 检查 parseConfig 函数 + parseConfigPattern := regexp.MustCompile(`func\s+parseConfig\s*\(`) + hasParseConfig := parseConfigPattern.MatchString(codeWithoutComments) + + if report.HasConfig && !hasParseConfig { + report.Issues = append(report.Issues, ValidationIssue{ + Category: "recommended", + Type: "config", + Message: "定义了配置结构体但缺少 parseConfig 函数", + Suggestion: "实现 parseConfig 函数来解析配置: func parseConfig(json gjson.Result, config *MyPluginConfig, log wrapper.Log) error", + Impact: "parseConfig 函数负责将 JSON 配置解析到结构体,是配置系统的核心", + }) + } + + // 检查回调函数 + callbacks := map[string]*regexp.Regexp{ + "onHttpRequestHeaders": regexp.MustCompile(`func\s+onHttpRequestHeaders\s*\(`), + "onHttpRequestBody": regexp.MustCompile(`func\s+onHttpRequestBody\s*\(`), + "onHttpResponseHeaders": regexp.MustCompile(`func\s+onHttpResponseHeaders\s*\(`), + "onHttpResponseBody": regexp.MustCompile(`func\s+onHttpResponseBody\s*\(`), + } + + for name, pattern := range callbacks { + if pattern.MatchString(codeWithoutComments) { + report.FoundCallbacks = append(report.FoundCallbacks, name) + } + } + + if len(report.FoundCallbacks) == 0 { + report.Issues = append(report.Issues, ValidationIssue{ + Category: "required", + Type: "api_usage", + Message: "未找到任何 HTTP 回调函数实现", + Suggestion: "至少实现一个回调函数,如: func onHttpRequestHeaders(ctx wrapper.HttpContext, config MyPluginConfig, log wrapper.Log) types.Action", + Impact: "回调函数是插件逻辑的核心,没有回调函数插件将不会执行任何操作", + }) + } + + // 检查错误处理 + errHandlingCount := strings.Count(codeWithoutComments, "if err != nil") + funcCount := strings.Count(codeWithoutComments, "func ") + if funcCount > 3 && errHandlingCount == 0 { + report.Issues = append(report.Issues, ValidationIssue{ + Category: "best_practice", + Type: "error_handling", + Message: "代码中缺少错误处理", + Suggestion: "对可能返回错误的操作添加错误检查: if err != nil { ... }", + Impact: "良好的错误处理可以提高插件的健壮性和可调试性", + }) + } + + // 检查日志记录 + hasLogging := strings.Contains(codeWithoutComments, "proxywasm.Log") || + strings.Contains(codeWithoutComments, "log.Error") || + strings.Contains(codeWithoutComments, "log.Warn") || + strings.Contains(codeWithoutComments, "log.Info") || + strings.Contains(codeWithoutComments, "log.Debug") + + if !hasLogging { + report.Issues = append(report.Issues, ValidationIssue{ + Category: "best_practice", + Type: "logging", + Message: "代码中没有日志记录", + Suggestion: "添加适当的日志记录,如: proxywasm.LogInfo(), log.Errorf() 等", + Impact: "日志记录有助于调试、监控和问题排查", + }) + } + + // 检查回调函数的返回值 + checkCallbackReturnErrors(&report, codeWithoutComments, report.FoundCallbacks) + + // 生成总体评估摘要 + report.Summary = generateValidationSummary(report) + + return report +} + +// removeComments 移除 Go 代码中的注释 +func removeComments(code string) string { + // 移除单行注释 + singleLineComment := regexp.MustCompile(`//.*`) + code = singleLineComment.ReplaceAllString(code, "") + + // 移除多行注释 + multiLineComment := regexp.MustCompile(`(?s)/\*.*?\*/`) + code = multiLineComment.ReplaceAllString(code, "") + + return code +} + +// containsImport 检查是否包含特定的 import +func containsImport(code, importPath string) bool { + // 匹配 import "path" 或 import ("path") + pattern := regexp.MustCompile(`import\s+(?:\([\s\S]*?)?["` + "`" + `]` + + regexp.QuoteMeta(importPath) + `["` + "`" + `]`) + return pattern.MatchString(code) +} + +// checkCallbackReturnErrors 检查回调函数的返回值错误 +func checkCallbackReturnErrors(report *ValidationReport, code string, foundCallbacks []string) { + // 检查回调函数内是否有 return nil(应该返回 types.Action) + for _, callback := range foundCallbacks { + // 提取回调函数体(简化的检查) + funcPattern := regexp.MustCompile( + `func\s+` + callback + `\s*\([^)]*\)\s+types\.Action\s*\{[^}]*return\s+nil[^}]*\}`) + if funcPattern.MatchString(code) { + report.Issues = append(report.Issues, ValidationIssue{ + Category: "required", + Type: "api_usage", + Message: fmt.Sprintf("回调函数 %s 不应返回 nil", callback), + Suggestion: "回调函数应返回 types.Action,如: return types.ActionContinue", + Impact: "返回 nil 会导致编译错误或运行时异常", + }) + break // 只报告一次 + } + } + + // 检查是否正确返回 types.Action + if len(foundCallbacks) > 0 { + hasActionReturn := strings.Contains(code, "types.ActionContinue") || + strings.Contains(code, "types.ActionPause") || + strings.Contains(code, "types.ActionSuspend") + + if !hasActionReturn { + report.Issues = append(report.Issues, ValidationIssue{ + Category: "recommended", + Type: "api_usage", + Message: "未找到明确的 Action 返回值", + Suggestion: "回调函数应返回明确的 types.Action 值(ActionContinue、ActionPause 等)", + Impact: "明确的返回值有助于代码可读性和正确性", + }) + } + } +} + +// generateValidationSummary 生成验证摘要 +func generateValidationSummary(report ValidationReport) string { + requiredIssues := 0 + recommendedIssues := 0 + optionalIssues := 0 + bestPracticeIssues := 0 + + for _, issue := range report.Issues { + switch issue.Category { + case "required": + requiredIssues++ + case "recommended": + recommendedIssues++ + case "optional": + optionalIssues++ + case "best_practice": + bestPracticeIssues++ + } + } + + if requiredIssues > 0 { + return fmt.Sprintf("代码存在 %d 个必须修复的问题,%d 个建议修复的问题,%d 个可选优化项,%d 个最佳实践建议。请优先解决必须修复的问题。", + requiredIssues, recommendedIssues, optionalIssues, bestPracticeIssues) + } + + if recommendedIssues > 0 { + return fmt.Sprintf("代码基本结构正确,但有 %d 个建议修复的问题,%d 个可选优化项,%d 个最佳实践建议。", + recommendedIssues, optionalIssues, bestPracticeIssues) + } + + if optionalIssues > 0 || bestPracticeIssues > 0 { + return fmt.Sprintf("代码结构良好,有 %d 个可选优化项和 %d 个最佳实践建议可以考虑。", + optionalIssues, bestPracticeIssues) + } + + callbacksInfo := "" + if len(report.FoundCallbacks) > 0 { + callbacksInfo = fmt.Sprintf(",实现了 %d 个回调函数", len(report.FoundCallbacks)) + } + + return fmt.Sprintf("代码验证通过,未发现明显问题%s。", callbacksInfo) +} + +// GenerateDeploymentPackage 生成部署配置提示(由 LLM 根据代码生成具体内容) +func GenerateDeploymentPackage(pluginName, goCode, configSchema, namespace string) DeploymentPackage { + // 所有配置文件都由 LLM 根据实际代码生成,不使用固定模板 + return DeploymentPackage{ + WasmPluginYAML: "", // LLM 生成 + Makefile: "", // LLM 生成 + Dockerfile: "", // LLM 生成 + ConfigMap: "", // LLM 生成 + README: "", // LLM 生成 + TestScript: "", // LLM 生成 + Dependencies: make(map[string]string), + } +} + +// FormatToolResultWithAIContext 格式化工具结果 +func FormatToolResultWithAIContext(userMessage, aiInstructions string, structuredData interface{}) ToolResult { + jsonData, _ := json.MarshalIndent(structuredData, "", " ") + output := fmt.Sprintf("%s\n\n%s\n\n%s", userMessage, aiInstructions, string(jsonData)) + return ToolResult{ + Content: []Content{{Type: "text", Text: output}}, + } +}