diff --git a/descriptions/descriptions.go b/descriptions/descriptions.go index 0cbf246..ea7ce99 100644 --- a/descriptions/descriptions.go +++ b/descriptions/descriptions.go @@ -157,9 +157,7 @@ const SearchOntapEndpoints = `Search the catalog by keyword across endpoint path const DescribeOntapEndpoint = `Get filterable query params for an endpoint. Call before ontap_get to learn valid filter names and which sub-objects need explicit fields (e.g. "space.*", "efficiency.*"). Pass cluster_name to automatically filter out fields and filters not available in that cluster's ONTAP version.` -const CreateSVM = `Create an SVM on a cluster by cluster name.` -const UpdateSVM = `Update an SVM on a cluster by cluster name.` -const DeleteSVM = `Delete an SVM on a cluster by cluster name.` +const SVMOperation = `Create, Update and Delete operations on SVM on a cluster by cluster name.` const DeleteSVMPeer = `Delete an SVM peer on a cluster by cluster name and local SVM name. The peer relationship UUID is looked up internally using the svm.name filter.` const OntapGet = `Execute a read-only GET against any ONTAP REST endpoint. diff --git a/server/server.go b/server/server.go index 4fbc36c..d35045a 100644 --- a/server/server.go +++ b/server/server.go @@ -147,9 +147,7 @@ func (a *App) createMCPServer() *mcp.Server { addTool(a, server, "delete_nfs_export_policies_rules", descriptions.DeleteNFSExportPolicyRules, deleteAnnotation, a.DeleteNFSExportPoliciesRule) // operation on SVM object - addTool(a, server, "create_svm", descriptions.CreateSVM, createAnnotation, a.CreateSVM) - addTool(a, server, "update_svm", descriptions.UpdateSVM, updateAnnotation, a.UpdateSVM) - addTool(a, server, "delete_svm", descriptions.DeleteSVM, deleteAnnotation, a.DeleteSVM) + addTool(a, server, "svm_operation", descriptions.SVMOperation, operationAnnotation, a.SVMOperation) // operation on SVM peer object addTool(a, server, "delete_svm_peer", descriptions.DeleteSVMPeer, deleteAnnotation, a.DeleteSVMPeer) @@ -656,6 +654,9 @@ var ( DestructiveHint: new(true), IdempotentHint: true, } + operationAnnotation = mcp.ToolAnnotations{ + DestructiveHint: new(true), + } ) func addTool[In, Out any](a *App, server *mcp.Server, name string, description string, annotations mcp.ToolAnnotations, handler mcp.ToolHandlerFor[In, Out]) { diff --git a/server/svm.go b/server/svm.go index be01736..4d449e0 100644 --- a/server/svm.go +++ b/server/svm.go @@ -9,105 +9,69 @@ import ( "github.com/netapp/ontap-mcp/tool" ) -func (a *App) CreateSVM(ctx context.Context, _ *mcp.CallToolRequest, parameters tool.SVMCreate) (*mcp.CallToolResult, any, error) { +func (a *App) SVMOperation(ctx context.Context, _ *mcp.CallToolRequest, parameters tool.SVMOperation) (*mcp.CallToolResult, any, error) { if !a.locks.TryLock(parameters.Cluster) { return errorResult(fmt.Errorf("another write operation is in progress on cluster %s, please try again", parameters.Cluster)), nil, nil } defer a.locks.Unlock(parameters.Cluster) - svmCreate, err := newCreateSVM(parameters) - if err != nil { - return nil, nil, err - } - - client, err := a.getClient(parameters.Cluster) - if err != nil { - return errorResult(err), nil, err - } - - err = client.CreateSVM(ctx, svmCreate) - if err != nil { - return errorResult(err), nil, err - } - - return &mcp.CallToolResult{ - Content: []mcp.Content{ - &mcp.TextContent{Text: "SVM created successfully"}, - }, - }, nil, nil -} - -func (a *App) UpdateSVM(ctx context.Context, _ *mcp.CallToolRequest, parameters tool.SVM) (*mcp.CallToolResult, any, error) { - if !a.locks.TryLock(parameters.Cluster) { - return errorResult(fmt.Errorf("another write operation is in progress on cluster %s, please try again", parameters.Cluster)), nil, nil - } - defer a.locks.Unlock(parameters.Cluster) - - svmUpdate, err := newUpdateSVM(parameters) - if err != nil { - return nil, nil, err - } - client, err := a.getClient(parameters.Cluster) if err != nil { return errorResult(err), nil, err } - err = client.UpdateSVM(ctx, svmUpdate, parameters.Name) - if err != nil { - return errorResult(err), nil, err - } - - return &mcp.CallToolResult{ - Content: []mcp.Content{ - &mcp.TextContent{Text: "SVM updated successfully"}, - }, - }, nil, nil -} - -func (a *App) DeleteSVM(ctx context.Context, _ *mcp.CallToolRequest, parameters tool.SVM) (*mcp.CallToolResult, any, error) { - if !a.locks.TryLock(parameters.Cluster) { - return errorResult(fmt.Errorf("another write operation is in progress on cluster %s, please try again", parameters.Cluster)), nil, nil - } - defer a.locks.Unlock(parameters.Cluster) - if parameters.Name == "" { return nil, nil, errors.New("SVM name is required") } - client, err := a.getClient(parameters.Cluster) - if err != nil { - return errorResult(err), nil, err + switch parameters.Operation { + case "create": + svmCreate := ontap.SVMCreate{Name: parameters.Name} + err = client.CreateSVM(ctx, svmCreate) + if err != nil { + return errorResult(err), nil, err + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: "SVM created successfully"}, + }, + }, nil, nil + case "update": + svmUpdate, err := updateSVMValidation(parameters.SVMUpdate) + if err != nil { + return nil, nil, err + } + + err = client.UpdateSVM(ctx, svmUpdate, parameters.Name) + if err != nil { + return errorResult(err), nil, err + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: "SVM updated successfully"}, + }, + }, nil, nil + case "delete": + err = client.DeleteSVM(ctx, parameters.Name) + if err != nil { + return errorResult(err), nil, err + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: "SVM deleted successfully"}, + }, + }, nil, nil + default: + return errorResult(fmt.Errorf("unsupported type_operation %q; supported values: create, update, delete", parameters.Operation)), nil, nil } - - err = client.DeleteSVM(ctx, parameters.Name) - if err != nil { - return errorResult(err), nil, err - } - - return &mcp.CallToolResult{ - Content: []mcp.Content{ - &mcp.TextContent{Text: "SVM deleted successfully"}, - }, - }, nil, nil } -func newCreateSVM(in tool.SVMCreate) (ontap.SVMCreate, error) { - out := ontap.SVMCreate{} - if in.Name == "" { - return out, errors.New("SVM name is required") - } - out.Name = in.Name - return out, nil -} - -func newUpdateSVM(in tool.SVM) (ontap.SVM, error) { +func updateSVMValidation(in tool.SVMUpdate) (ontap.SVM, error) { out := ontap.SVM{} - if in.Name == "" { - return out, errors.New("SVM name is required") - } - hasUpdate := false if in.NewName != "" { out.Name = in.NewName diff --git a/tool/tool.go b/tool/tool.go index 46b83d9..5e766a6 100644 --- a/tool/tool.go +++ b/tool/tool.go @@ -351,14 +351,14 @@ type DNSServiceDelete struct { SkipConfigValidation bool `json:"skip_config_validation,omitzero" jsonschema:"Indicates whether or not the validation for the specified DNS configuration is disabled."` } -type SVMCreate struct { - Cluster string `json:"cluster_name" jsonschema:"cluster name"` - Name string `json:"svm_name" jsonschema:"SVM name"` +type SVMOperation struct { + Cluster string `json:"cluster_name" jsonschema:"cluster name"` + Operation string `json:"operation" jsonschema:"SVM operation type (e.g., create, update, delete)"` + Name string `json:"svm_name" jsonschema:"SVM name"` + SVMUpdate SVMUpdate `json:"svm_update,omitzero" jsonschema:"update SVM operation"` } -type SVM struct { - Cluster string `json:"cluster_name" jsonschema:"cluster name"` - Name string `json:"svm_name" jsonschema:"SVM name"` +type SVMUpdate struct { NewName string `json:"new_name,omitzero" jsonschema:"new name of SVM"` State string `json:"state,omitzero" jsonschema:"state of SVM (e.g., starting, running, stopping, stopped, deleting, initializing)"` Comment string `json:"comment,omitzero" jsonschema:"comment"`