Skip to content

fix(mcp): prevent panic on nil interface conversion for tool arguments#1622

Merged
0xJacky merged 3 commits intodevfrom
cursor/interface-conversion-error-36ec
Apr 4, 2026
Merged

fix(mcp): prevent panic on nil interface conversion for tool arguments#1622
0xJacky merged 3 commits intodevfrom
cursor/interface-conversion-error-36ec

Conversation

@0xJacky
Copy link
Copy Markdown
Owner

@0xJacky 0xJacky commented Apr 4, 2026

Bug Fix

This PR fixes the panic that occurs when MCP config tools are called via Claude Desktop with protocol version 2025-11-25.

Problem

All MCP configuration tools panic with:

interface conversion: interface {} is nil, not string

This happens because the tool handlers used direct type assertions like args["relative_path"].(string) which panic when the argument value is nil (not provided).

Solution

  • Created helper functions in internal/mcp/args.go for safe argument extraction:

    • GetString() - safely extracts string values, returns empty string if nil
    • GetBool() - safely extracts boolean values, returns false if nil
    • GetSlice() - safely extracts slice values, returns nil if nil
  • Updated all affected config handlers to use these safe extraction functions

Affected Tools

  • nginx_config_list
  • nginx_config_get
  • nginx_config_add
  • nginx_config_modify
  • nginx_config_rename
  • nginx_config_mkdir
  • nginx_config_history
  • nginx_config_enable

Testing

  • Verified code compiles with go build
  • Verified code formatting with gofmt and goimports
  • Verified code passes go vet
Open in Web Open in Cursor 

Add safe argument extraction helper functions in internal/mcp/args.go
that handle nil values gracefully instead of panicking on direct type
assertions.

This fixes the issue where MCP config tools panic with:
'interface conversion: interface {} is nil, not string'
when called via Claude Desktop with protocol version 2025-11-25.

Affected tools:
- nginx_config_list
- nginx_config_get
- nginx_config_add
- nginx_config_modify
- nginx_config_rename
- nginx_config_mkdir
- nginx_config_history
- nginx_config_enable

Fixes #36ec

Co-authored-by: Jacky <me@jackyu.cn>
…ta loss

The safe argument extraction helpers (mcp.GetString, etc.) return zero values
for nil/missing arguments, which could cause silent data loss. This adds
explicit validation for required arguments in:

- config_modify: validate relative_path and content
- config_add: validate name and content
- config_rename: validate orig_name and new_name
- config_mkdir: validate folder_name

This follows the same pattern already used in handleNginxConfigEnable.
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Missing required argument validation in config_get
    • Added empty string validation for relative_path in config_get and filepath in config_history handlers, consistent with other handlers in the codebase.
Preview (376f14a479)
diff --git a/internal/mcp/args.go b/internal/mcp/args.go
new file mode 100644
--- /dev/null
+++ b/internal/mcp/args.go
@@ -1,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
--- a/mcp/config/config_add.go
+++ b/mcp/config/config_add.go
@@ -4,14 +4,16 @@
 	"context"
 	"encoding/json"
 	"errors"
+	"fmt"
 	"os"
 
 	"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 +21,36 @@
 // 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")
 
+	if name == "" {
+		return nil, fmt.Errorf("argument 'name' is required")
+	}
+	if _, exists := args["content"]; !exists || args["content"] == nil {
+		return nil, fmt.Errorf("argument 'content' is required")
+	}
+
 	// 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 +116,5 @@
 	}
 
 	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
--- a/mcp/config/config_enable.go
+++ b/mcp/config/config_enable.go
@@ -9,25 +9,26 @@
 
 	"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 @@
 	}
 	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
--- a/mcp/config/config_get.go
+++ b/mcp/config/config_get.go
@@ -3,26 +3,32 @@
 import (
 	"context"
 	"encoding/json"
+	"fmt"
 	"os"
 	"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")
 
+	if relativePath == "" {
+		return nil, fmt.Errorf("argument 'relative_path' is required")
+	}
+
 	absPath, err := config.ResolveAbsoluteOrRelativeConfPath(relativePath)
 	if err != nil {
 		return nil, err
@@ -55,5 +61,5 @@
 	}
 
 	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
--- a/mcp/config/config_history.go
+++ b/mcp/config/config_history.go
@@ -3,23 +3,29 @@
 import (
 	"context"
 	"encoding/json"
+	"fmt"
 
+	"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")
 
+	if filepath == "" {
+		return nil, fmt.Errorf("argument 'filepath' is required")
+	}
+
 	q := query.ConfigBackup
 	var histories, err = q.Where(q.FilePath.Eq(filepath)).Order(q.ID.Desc()).Find()
 	if err != nil {
@@ -27,5 +33,5 @@
 	}
 
 	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
--- a/mcp/config/config_list.go
+++ b/mcp/config/config_list.go
@@ -7,27 +7,28 @@
 	"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
--- a/mcp/config/config_mkdir.go
+++ b/mcp/config/config_mkdir.go
@@ -3,26 +3,32 @@
 import (
 	"context"
 	"encoding/json"
+	"fmt"
 	"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")
 
+	if folderName == "" {
+		return nil, fmt.Errorf("argument 'folder_name' is required")
+	}
+
 	fullPath, err := config.ResolveConfPath(basePath, folderName)
 	if err != nil {
 		return nil, err
@@ -39,5 +45,5 @@
 	}
 
 	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
--- a/mcp/config/config_modify.go
+++ b/mcp/config/config_modify.go
@@ -4,13 +4,15 @@
 	"context"
 	"encoding/json"
 	"errors"
+	"fmt"
 	"path/filepath"
 
 	"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 +21,34 @@
 // 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")
 
+	if relativePath == "" {
+		return nil, fmt.Errorf("argument 'relative_path' is required")
+	}
+	if _, exists := args["content"]; !exists || args["content"] == nil {
+		return nil, fmt.Errorf("argument 'content' is required")
+	}
+
 	// 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 +99,5 @@
 	}
 
 	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
--- a/mcp/config/config_rename.go
+++ b/mcp/config/config_rename.go
@@ -3,42 +3,49 @@
 import (
 	"context"
 	"encoding/json"
+	"fmt"
 	"os"
 	"path/filepath"
 	"strings"
 
 	"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")
 
+	if origName == "" {
+		return nil, fmt.Errorf("argument 'orig_name' is required")
+	}
+	if newName == "" {
+		return nil, fmt.Errorf("argument 'new_name' is required")
+	}
+
 	// 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 +54,7 @@
 			"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 +125,5 @@
 	}
 
 	jsonResult, _ := json.Marshal(result)
-	return mcp.NewToolResultText(string(jsonResult)), nil
+	return mcpgo.NewToolResultText(string(jsonResult)), nil
 }

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit b787572. Configure here.

@0xJacky 0xJacky merged commit d454a2a into dev Apr 4, 2026
48 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants