diff --git a/internal/mcp/args.go b/internal/mcp/args.go new file mode 100644 index 00000000..35cb5161 --- /dev/null +++ b/internal/mcp/args.go @@ -0,0 +1,34 @@ +package mcp + +// GetString safely extracts a string value from the arguments map. +// Returns an empty string if the key doesn't exist or the value is nil. +func GetString(args map[string]interface{}, key string) string { + if v, ok := args[key]; ok && v != nil { + if s, ok := v.(string); ok { + return s + } + } + return "" +} + +// GetBool safely extracts a boolean value from the arguments map. +// Returns false if the key doesn't exist or the value is nil. +func GetBool(args map[string]interface{}, key string) bool { + if v, ok := args[key]; ok && v != nil { + if b, ok := v.(bool); ok { + return b + } + } + return false +} + +// GetSlice safely extracts a slice of interface{} from the arguments map. +// Returns nil if the key doesn't exist or the value is nil. +func GetSlice(args map[string]interface{}, key string) []interface{} { + if v, ok := args[key]; ok && v != nil { + if s, ok := v.([]interface{}); ok { + return s + } + } + return nil +} diff --git a/mcp/config/config_add.go b/mcp/config/config_add.go index 0b6c240b..07675238 100644 --- a/mcp/config/config_add.go +++ b/mcp/config/config_add.go @@ -8,10 +8,11 @@ import ( "github.com/0xJacky/Nginx-UI/internal/config" "github.com/0xJacky/Nginx-UI/internal/helper" + "github.com/0xJacky/Nginx-UI/internal/mcp" "github.com/0xJacky/Nginx-UI/internal/nginx" "github.com/0xJacky/Nginx-UI/model" "github.com/0xJacky/Nginx-UI/query" - "github.com/mark3labs/mcp-go/mcp" + mcpgo "github.com/mark3labs/mcp-go/mcp" ) const nginxConfigAddToolName = "nginx_config_add" @@ -19,31 +20,29 @@ const nginxConfigAddToolName = "nginx_config_add" // ErrFileAlreadyExists is returned when trying to create a file that already exists var ErrFileAlreadyExists = errors.New("file already exists") -var nginxConfigAddTool = mcp.NewTool( +var nginxConfigAddTool = mcpgo.NewTool( nginxConfigAddToolName, - mcp.WithDescription("Add or create a new Nginx configuration file"), - mcp.WithString("name", mcp.Description("The name of the configuration file to create")), - mcp.WithString("content", mcp.Description("The content of the configuration file")), - mcp.WithString("base_dir", mcp.Description("The base directory for the configuration")), - mcp.WithBoolean("overwrite", mcp.Description("Whether to overwrite an existing file")), - mcp.WithArray("sync_node_ids", mcp.Description("IDs of nodes to sync the configuration to")), + mcpgo.WithDescription("Add or create a new Nginx configuration file"), + mcpgo.WithString("name", mcpgo.Description("The name of the configuration file to create")), + mcpgo.WithString("content", mcpgo.Description("The content of the configuration file")), + mcpgo.WithString("base_dir", mcpgo.Description("The base directory for the configuration")), + mcpgo.WithBoolean("overwrite", mcpgo.Description("Whether to overwrite an existing file")), + mcpgo.WithArray("sync_node_ids", mcpgo.Description("IDs of nodes to sync the configuration to")), ) -func handleNginxConfigAdd(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { +func handleNginxConfigAdd(ctx context.Context, request mcpgo.CallToolRequest) (*mcpgo.CallToolResult, error) { args := request.GetArguments() - name := args["name"].(string) - content := args["content"].(string) - baseDir := args["base_dir"].(string) - overwrite := args["overwrite"].(bool) + name := mcp.GetString(args, "name") + content := mcp.GetString(args, "content") + baseDir := mcp.GetString(args, "base_dir") + overwrite := mcp.GetBool(args, "overwrite") // Convert sync_node_ids from []interface{} to []uint64 - syncNodeIdsInterface, ok := args["sync_node_ids"].([]interface{}) + syncNodeIdsInterface := mcp.GetSlice(args, "sync_node_ids") syncNodeIds := make([]uint64, 0) - if ok { - for _, id := range syncNodeIdsInterface { - if idFloat, ok := id.(float64); ok { - syncNodeIds = append(syncNodeIds, uint64(idFloat)) - } + for _, id := range syncNodeIdsInterface { + if idFloat, ok := id.(float64); ok { + syncNodeIds = append(syncNodeIds, uint64(idFloat)) } } @@ -109,5 +108,5 @@ func handleNginxConfigAdd(ctx context.Context, request mcp.CallToolRequest) (*mc } jsonResult, _ := json.Marshal(result) - return mcp.NewToolResultText(string(jsonResult)), nil + return mcpgo.NewToolResultText(string(jsonResult)), nil } diff --git a/mcp/config/config_enable.go b/mcp/config/config_enable.go index e9b8daf3..75f1b8f7 100644 --- a/mcp/config/config_enable.go +++ b/mcp/config/config_enable.go @@ -9,25 +9,26 @@ import ( "github.com/0xJacky/Nginx-UI/internal/config" "github.com/0xJacky/Nginx-UI/internal/helper" + "github.com/0xJacky/Nginx-UI/internal/mcp" "github.com/0xJacky/Nginx-UI/internal/nginx" - "github.com/mark3labs/mcp-go/mcp" + mcpgo "github.com/mark3labs/mcp-go/mcp" ) const nginxConfigEnableToolName = "nginx_config_enable" -var nginxConfigEnableTool = mcp.NewTool( +var nginxConfigEnableTool = mcpgo.NewTool( nginxConfigEnableToolName, - mcp.WithDescription("Enable a previously created Nginx configuration (creates symlink in sites-enabled)"), - mcp.WithString("name", mcp.Description("The name of the configuration file to enable")), - mcp.WithString("base_dir", mcp.Description("The source directory (default: sites-available)")), - mcp.WithBoolean("overwrite", mcp.Description("Whether to overwrite an existing enabled configuration")), + mcpgo.WithDescription("Enable a previously created Nginx configuration (creates symlink in sites-enabled)"), + mcpgo.WithString("name", mcpgo.Description("The name of the configuration file to enable")), + mcpgo.WithString("base_dir", mcpgo.Description("The source directory (default: sites-available)")), + mcpgo.WithBoolean("overwrite", mcpgo.Description("Whether to overwrite an existing enabled configuration")), ) -func handleNginxConfigEnable(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { +func handleNginxConfigEnable(ctx context.Context, request mcpgo.CallToolRequest) (*mcpgo.CallToolResult, error) { args := request.GetArguments() - name := args["name"].(string) - baseDir := args["base_dir"].(string) - overwrite := args["overwrite"].(bool) + name := mcp.GetString(args, "name") + baseDir := mcp.GetString(args, "base_dir") + overwrite := mcp.GetBool(args, "overwrite") if name == "" { return nil, fmt.Errorf("argument 'name' is required") @@ -110,6 +111,6 @@ func handleNginxConfigEnable(ctx context.Context, request mcp.CallToolRequest) ( } jsonResult, _ := json.Marshal(result) - return mcp.NewToolResultText(string(jsonResult)), nil + return mcpgo.NewToolResultText(string(jsonResult)), nil } diff --git a/mcp/config/config_get.go b/mcp/config/config_get.go index f5eecca3..cc31fc4f 100644 --- a/mcp/config/config_get.go +++ b/mcp/config/config_get.go @@ -7,21 +7,22 @@ import ( "path/filepath" "github.com/0xJacky/Nginx-UI/internal/config" + "github.com/0xJacky/Nginx-UI/internal/mcp" "github.com/0xJacky/Nginx-UI/query" - "github.com/mark3labs/mcp-go/mcp" + mcpgo "github.com/mark3labs/mcp-go/mcp" ) const nginxConfigGetToolName = "nginx_config_get" -var nginxConfigGetTool = mcp.NewTool( +var nginxConfigGetTool = mcpgo.NewTool( nginxConfigGetToolName, - mcp.WithDescription("Get a specific Nginx configuration file"), - mcp.WithString("relative_path", mcp.Description("The relative path to the configuration file")), + mcpgo.WithDescription("Get a specific Nginx configuration file"), + mcpgo.WithString("relative_path", mcpgo.Description("The relative path to the configuration file")), ) -func handleNginxConfigGet(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { +func handleNginxConfigGet(ctx context.Context, request mcpgo.CallToolRequest) (*mcpgo.CallToolResult, error) { args := request.GetArguments() - relativePath := args["relative_path"].(string) + relativePath := mcp.GetString(args, "relative_path") absPath, err := config.ResolveAbsoluteOrRelativeConfPath(relativePath) if err != nil { @@ -55,5 +56,5 @@ func handleNginxConfigGet(ctx context.Context, request mcp.CallToolRequest) (*mc } jsonResult, _ := json.Marshal(result) - return mcp.NewToolResultText(string(jsonResult)), nil + return mcpgo.NewToolResultText(string(jsonResult)), nil } diff --git a/mcp/config/config_history.go b/mcp/config/config_history.go index c321cda5..167a6ef1 100644 --- a/mcp/config/config_history.go +++ b/mcp/config/config_history.go @@ -4,21 +4,22 @@ import ( "context" "encoding/json" + "github.com/0xJacky/Nginx-UI/internal/mcp" "github.com/0xJacky/Nginx-UI/query" - "github.com/mark3labs/mcp-go/mcp" + mcpgo "github.com/mark3labs/mcp-go/mcp" ) const nginxConfigHistoryToolName = "nginx_config_history" -var nginxConfigHistoryTool = mcp.NewTool( +var nginxConfigHistoryTool = mcpgo.NewTool( nginxConfigHistoryToolName, - mcp.WithDescription("Get history of Nginx configuration changes"), - mcp.WithString("filepath", mcp.Description("The file path to get history for")), + mcpgo.WithDescription("Get history of Nginx configuration changes"), + mcpgo.WithString("filepath", mcpgo.Description("The file path to get history for")), ) -func handleNginxConfigHistory(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { +func handleNginxConfigHistory(ctx context.Context, request mcpgo.CallToolRequest) (*mcpgo.CallToolResult, error) { args := request.GetArguments() - filepath := args["filepath"].(string) + filepath := mcp.GetString(args, "filepath") q := query.ConfigBackup var histories, err = q.Where(q.FilePath.Eq(filepath)).Order(q.ID.Desc()).Find() @@ -27,5 +28,5 @@ func handleNginxConfigHistory(ctx context.Context, request mcp.CallToolRequest) } jsonResult, _ := json.Marshal(histories) - return mcp.NewToolResultText(string(jsonResult)), nil + return mcpgo.NewToolResultText(string(jsonResult)), nil } diff --git a/mcp/config/config_list.go b/mcp/config/config_list.go index 694c2225..91782dc7 100644 --- a/mcp/config/config_list.go +++ b/mcp/config/config_list.go @@ -7,27 +7,28 @@ import ( "strings" "github.com/0xJacky/Nginx-UI/internal/config" - "github.com/mark3labs/mcp-go/mcp" + "github.com/0xJacky/Nginx-UI/internal/mcp" + mcpgo "github.com/mark3labs/mcp-go/mcp" ) const nginxConfigListToolName = "nginx_config_list" -var nginxConfigListTool = mcp.NewTool( +var nginxConfigListTool = mcpgo.NewTool( nginxConfigListToolName, - mcp.WithDescription("This is the list of Nginx configurations"), - mcp.WithString("relative_path", mcp.Description("The relative path to the Nginx configurations")), - mcp.WithString("filter_by_name", mcp.Description("Filter the Nginx configurations by name")), + mcpgo.WithDescription("This is the list of Nginx configurations"), + mcpgo.WithString("relative_path", mcpgo.Description("The relative path to the Nginx configurations")), + mcpgo.WithString("filter_by_name", mcpgo.Description("Filter the Nginx configurations by name")), ) -func handleNginxConfigList(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { +func handleNginxConfigList(ctx context.Context, request mcpgo.CallToolRequest) (*mcpgo.CallToolResult, error) { args := request.GetArguments() - relativePath := args["relative_path"].(string) - filterByName := args["filter_by_name"].(string) + relativePath := mcp.GetString(args, "relative_path") + filterByName := mcp.GetString(args, "filter_by_name") configs, err := config.GetConfigList(relativePath, func(file os.FileInfo) bool { return filterByName == "" || strings.Contains(file.Name(), filterByName) }) jsonResult, _ := json.Marshal(configs) - return mcp.NewToolResultText(string(jsonResult)), err + return mcpgo.NewToolResultText(string(jsonResult)), err } diff --git a/mcp/config/config_mkdir.go b/mcp/config/config_mkdir.go index 0b1e5b48..6fd382a4 100644 --- a/mcp/config/config_mkdir.go +++ b/mcp/config/config_mkdir.go @@ -6,22 +6,23 @@ import ( "os" "github.com/0xJacky/Nginx-UI/internal/config" - "github.com/mark3labs/mcp-go/mcp" + "github.com/0xJacky/Nginx-UI/internal/mcp" + mcpgo "github.com/mark3labs/mcp-go/mcp" ) const nginxConfigMkdirToolName = "nginx_config_mkdir" -var nginxConfigMkdirTool = mcp.NewTool( +var nginxConfigMkdirTool = mcpgo.NewTool( nginxConfigMkdirToolName, - mcp.WithDescription("Create a new directory in the Nginx configuration path"), - mcp.WithString("base_path", mcp.Description("The base path where to create the directory")), - mcp.WithString("folder_name", mcp.Description("The name of the folder to create")), + mcpgo.WithDescription("Create a new directory in the Nginx configuration path"), + mcpgo.WithString("base_path", mcpgo.Description("The base path where to create the directory")), + mcpgo.WithString("folder_name", mcpgo.Description("The name of the folder to create")), ) -func handleNginxConfigMkdir(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { +func handleNginxConfigMkdir(ctx context.Context, request mcpgo.CallToolRequest) (*mcpgo.CallToolResult, error) { args := request.GetArguments() - basePath := args["base_path"].(string) - folderName := args["folder_name"].(string) + basePath := mcp.GetString(args, "base_path") + folderName := mcp.GetString(args, "folder_name") fullPath, err := config.ResolveConfPath(basePath, folderName) if err != nil { @@ -39,5 +40,5 @@ func handleNginxConfigMkdir(ctx context.Context, request mcp.CallToolRequest) (* } jsonResult, _ := json.Marshal(result) - return mcp.NewToolResultText(string(jsonResult)), nil + return mcpgo.NewToolResultText(string(jsonResult)), nil } diff --git a/mcp/config/config_modify.go b/mcp/config/config_modify.go index fd1dfabe..993fdc44 100644 --- a/mcp/config/config_modify.go +++ b/mcp/config/config_modify.go @@ -8,9 +8,10 @@ import ( "github.com/0xJacky/Nginx-UI/internal/config" "github.com/0xJacky/Nginx-UI/internal/helper" + "github.com/0xJacky/Nginx-UI/internal/mcp" "github.com/0xJacky/Nginx-UI/model" "github.com/0xJacky/Nginx-UI/query" - "github.com/mark3labs/mcp-go/mcp" + mcpgo "github.com/mark3labs/mcp-go/mcp" "gorm.io/gen/field" ) @@ -19,29 +20,27 @@ const nginxConfigModifyToolName = "nginx_config_modify" // ErrFileNotFound is returned when a file is not found var ErrFileNotFound = errors.New("file not found") -var nginxConfigModifyTool = mcp.NewTool( +var nginxConfigModifyTool = mcpgo.NewTool( nginxConfigModifyToolName, - mcp.WithDescription("Modify an existing Nginx configuration file"), - mcp.WithString("relative_path", mcp.Description("The relative path to the configuration file")), - mcp.WithString("content", mcp.Description("The new content of the configuration file")), - mcp.WithBoolean("sync_overwrite", mcp.Description("Whether to overwrite existing files when syncing")), - mcp.WithArray("sync_node_ids", mcp.Description("IDs of nodes to sync the configuration to")), + mcpgo.WithDescription("Modify an existing Nginx configuration file"), + mcpgo.WithString("relative_path", mcpgo.Description("The relative path to the configuration file")), + mcpgo.WithString("content", mcpgo.Description("The new content of the configuration file")), + mcpgo.WithBoolean("sync_overwrite", mcpgo.Description("Whether to overwrite existing files when syncing")), + mcpgo.WithArray("sync_node_ids", mcpgo.Description("IDs of nodes to sync the configuration to")), ) -func handleNginxConfigModify(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { +func handleNginxConfigModify(ctx context.Context, request mcpgo.CallToolRequest) (*mcpgo.CallToolResult, error) { args := request.GetArguments() - relativePath := args["relative_path"].(string) - content := args["content"].(string) - syncOverwrite := args["sync_overwrite"].(bool) + relativePath := mcp.GetString(args, "relative_path") + content := mcp.GetString(args, "content") + syncOverwrite := mcp.GetBool(args, "sync_overwrite") // Convert sync_node_ids from []interface{} to []uint64 - syncNodeIdsInterface, ok := args["sync_node_ids"].([]interface{}) + syncNodeIdsInterface := mcp.GetSlice(args, "sync_node_ids") syncNodeIds := make([]uint64, 0) - if ok { - for _, id := range syncNodeIdsInterface { - if idFloat, ok := id.(float64); ok { - syncNodeIds = append(syncNodeIds, uint64(idFloat)) - } + for _, id := range syncNodeIdsInterface { + if idFloat, ok := id.(float64); ok { + syncNodeIds = append(syncNodeIds, uint64(idFloat)) } } @@ -92,5 +91,5 @@ func handleNginxConfigModify(ctx context.Context, request mcp.CallToolRequest) ( } jsonResult, _ := json.Marshal(result) - return mcp.NewToolResultText(string(jsonResult)), nil + return mcpgo.NewToolResultText(string(jsonResult)), nil } diff --git a/mcp/config/config_rename.go b/mcp/config/config_rename.go index eebe29d3..b26ddc30 100644 --- a/mcp/config/config_rename.go +++ b/mcp/config/config_rename.go @@ -9,36 +9,35 @@ import ( "github.com/0xJacky/Nginx-UI/internal/config" "github.com/0xJacky/Nginx-UI/internal/helper" + "github.com/0xJacky/Nginx-UI/internal/mcp" "github.com/0xJacky/Nginx-UI/model" "github.com/0xJacky/Nginx-UI/query" - "github.com/mark3labs/mcp-go/mcp" + mcpgo "github.com/mark3labs/mcp-go/mcp" ) const nginxConfigRenameToolName = "nginx_config_rename" -var nginxConfigRenameTool = mcp.NewTool( +var nginxConfigRenameTool = mcpgo.NewTool( nginxConfigRenameToolName, - mcp.WithDescription("Rename a file or directory in the Nginx configuration path"), - mcp.WithString("base_path", mcp.Description("The base path where the file or directory is located")), - mcp.WithString("orig_name", mcp.Description("The original name of the file or directory")), - mcp.WithString("new_name", mcp.Description("The new name for the file or directory")), - mcp.WithArray("sync_node_ids", mcp.Description("IDs of nodes to sync the rename operation to")), + mcpgo.WithDescription("Rename a file or directory in the Nginx configuration path"), + mcpgo.WithString("base_path", mcpgo.Description("The base path where the file or directory is located")), + mcpgo.WithString("orig_name", mcpgo.Description("The original name of the file or directory")), + mcpgo.WithString("new_name", mcpgo.Description("The new name for the file or directory")), + mcpgo.WithArray("sync_node_ids", mcpgo.Description("IDs of nodes to sync the rename operation to")), ) -func handleNginxConfigRename(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { +func handleNginxConfigRename(ctx context.Context, request mcpgo.CallToolRequest) (*mcpgo.CallToolResult, error) { args := request.GetArguments() - basePath := args["base_path"].(string) - origName := args["orig_name"].(string) - newName := args["new_name"].(string) + basePath := mcp.GetString(args, "base_path") + origName := mcp.GetString(args, "orig_name") + newName := mcp.GetString(args, "new_name") // Convert sync_node_ids from []interface{} to []uint64 - syncNodeIdsInterface, ok := args["sync_node_ids"].([]interface{}) + syncNodeIdsInterface := mcp.GetSlice(args, "sync_node_ids") syncNodeIds := make([]uint64, 0) - if ok { - for _, id := range syncNodeIdsInterface { - if idFloat, ok := id.(float64); ok { - syncNodeIds = append(syncNodeIds, uint64(idFloat)) - } + for _, id := range syncNodeIdsInterface { + if idFloat, ok := id.(float64); ok { + syncNodeIds = append(syncNodeIds, uint64(idFloat)) } } @@ -47,7 +46,7 @@ func handleNginxConfigRename(ctx context.Context, request mcp.CallToolRequest) ( "message": "No changes needed, names are identical", } jsonResult, _ := json.Marshal(result) - return mcp.NewToolResultText(string(jsonResult)), nil + return mcpgo.NewToolResultText(string(jsonResult)), nil } origFullPath, err := config.ResolveConfPath(basePath, origName) @@ -118,5 +117,5 @@ func handleNginxConfigRename(ctx context.Context, request mcp.CallToolRequest) ( } jsonResult, _ := json.Marshal(result) - return mcp.NewToolResultText(string(jsonResult)), nil + return mcpgo.NewToolResultText(string(jsonResult)), nil }