# Directory Structure ``` ├── .gitignore ├── go.mod ├── go.sum ├── internal │ ├── client │ │ ├── client.go │ │ └── modelcontextclient.go │ ├── codegen │ │ ├── cmd │ │ │ └── main.go │ │ └── templates │ │ ├── resources.go │ │ └── tools.go │ └── json │ ├── marshal_test.go │ └── marshal.go ├── LICENSE ├── main.go ├── Makefile ├── pkg │ ├── prompts │ │ └── credentials.go │ ├── resources │ │ ├── accesscontrolpolicy.go │ │ ├── addressgroup.go │ │ ├── availabilityzone.go │ │ ├── category.go │ │ ├── cluster.go │ │ ├── common.go │ │ ├── host.go │ │ ├── image.go │ │ ├── networksecurityrule.go │ │ ├── permission.go │ │ ├── project.go │ │ ├── protectionrule.go │ │ ├── recoveryplan.go │ │ ├── recoveryplanjob.go │ │ ├── role.go │ │ ├── servicegroup.go │ │ ├── subnet.go │ │ ├── user.go │ │ ├── usergroup.go │ │ ├── vm.go │ │ └── volumegroup.go │ └── tools │ ├── accesscontrolpolicy.go │ ├── addressgroup.go │ ├── apinamespace.go │ ├── category.go │ ├── cluster.go │ ├── common.go │ ├── host.go │ ├── image.go │ ├── networksecurityrule.go │ ├── permission.go │ ├── project.go │ ├── protectionrule.go │ ├── recoveryplan.go │ ├── recoveryplanjob.go │ ├── role.go │ ├── servicegroup.go │ ├── subnet.go │ ├── user.go │ ├── usergroup.go │ ├── vm.go │ └── volumegroup.go └── README.md ``` # Files -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` 1 | bin 2 | .DS_Store 3 | mcp.json 4 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | # MCP Nutanix 2 | 3 | A Model Context Protocol (MCP) server for interacting with Nutanix Prism Central APIs through Large Language Models (LLMs). 4 | 5 | ## ⚠️ Disclaimer 6 | 7 | **THIS IS AN EXPERIMENTAL PROJECT** 8 | 9 | This project was created as a personal project to explore the capabilities of the Model Context Protocol frameworks in Go. It is: 10 | 11 | - **NOT** an official Nutanix product or tool 12 | - **NOT** supported, endorsed, or maintained by Nutanix 13 | - **NOT** suitable for production environments 14 | - **PROVIDED AS-IS** with no warranties or guarantees 15 | 16 | **USE AT YOUR OWN RISK**: The author takes no responsibility for any issues, damages, or outages that may result from using this code. 17 | 18 | ## Overview 19 | 20 | This MCP server allows LLMs to interact with Nutanix Prism Central by: 21 | 22 | 1. Connecting to a Prism Central instance with user credentials 23 | 2. Listing various resources (VMs, Clusters, Hosts, etc.) 24 | 3. Retrieving specific resource details via URI-based access 25 | 26 | The implementation uses the [Prism Go Client](https://github.com/nutanix-cloud-native/prism-go-client) to communicate with Prism Central and the [MCP Go library](https://github.com/mark3labs/mcp-go) to implement the Model Context Protocol. 27 | 28 | ## Getting Started 29 | 30 | ### Prerequisites 31 | 32 | - Go 1.23 or higher 33 | - Access to a Nutanix Prism Central instance 34 | - Tools like `make` and `go fmt` for building 35 | 36 | ### Building 37 | 38 | ```bash 39 | # Clone the repository 40 | git clone https://github.com/thunderboltsid/mcp-nutanix.git 41 | cd mcp-nutanix 42 | 43 | # Build the MCP server 44 | make build 45 | ``` 46 | 47 | ## Credential Configuration 48 | 49 | The server supports two credential methods: 50 | 51 | 1. **Interactive credentials** (default) - Works with Claude via MCP prompts 52 | 2. **Static credentials** - Required for tools like Cursor that don't support interactive prompts 53 | 54 | ## MCP Client Configuration 55 | 56 | To use this server with MCP clients, you need to configure the client to connect to the server. 57 | 58 | ### Claude Desktop/Code 59 | 60 | Create or update `~/.anthropic/claude_desktop.json`: 61 | 62 | ```json 63 | { 64 | "mcpServers": { 65 | "nutanix": { 66 | "command": "/path/to/mcp-nutanix" 67 | } 68 | } 69 | } 70 | ``` 71 | 72 | Claude will prompt you for credentials when first using the server. 73 | 74 | ### Cursor 75 | 76 | For Cursor, you need to provide static credentials via environment variables since it doesn't support interactive prompts. 77 | 78 | Create or update `~/.cursor/mcp.json`: 79 | 80 | ```json 81 | { 82 | "mcpServers": { 83 | "nutanix": { 84 | "command": "/path/to/mcp-nutanix", 85 | "env": { 86 | "NUTANIX_ENDPOINT": "your-prism-central-ip-or-hostname", 87 | "NUTANIX_USERNAME": "your-username", 88 | "NUTANIX_PASSWORD": "your-password", 89 | "NUTANIX_INSECURE": "true" 90 | } 91 | } 92 | } 93 | } 94 | ``` 95 | 96 | **Environment Variables:** 97 | - `NUTANIX_ENDPOINT` - Prism Central IP or hostname (required) 98 | - `NUTANIX_USERNAME` - API username (required) 99 | - `NUTANIX_PASSWORD` - API password (required) 100 | - `NUTANIX_INSECURE` - Set to "true" for self-signed certificates (optional) 101 | 102 | ### Other MCP Clients 103 | 104 | This server follows the standard MCP protocol and should work with any MCP client that supports stdio transport. Refer to your client's documentation for configuration instructions. 105 | 106 | ## Usage 107 | 108 | Once the MCP server is configured with your client and connected to your Prism Central instance, LLMs can interact with it through the MCP protocol. 109 | 110 | ### Resource Listing 111 | 112 | To list resources, use the appropriate tool: 113 | 114 | ``` 115 | vms 116 | clusters 117 | hosts 118 | images 119 | subnets 120 | ``` 121 | 122 | The LLM will receive a JSON list of resources that it can parse and analyze. 123 | 124 | ### Resource Access 125 | 126 | To access a specific resource, use a resource URI: 127 | 128 | ``` 129 | vm://{uuid} 130 | cluster://{uuid} 131 | host://{uuid} 132 | ``` 133 | 134 | The LLM will receive detailed JSON information about the specific resource. 135 | 136 | ## Development 137 | 138 | ### Project Structure 139 | 140 | ``` 141 | mcp-nutanix/ 142 | ├── bin/ # Compiled binaries 143 | ├── internal/ # Internal packages 144 | │ ├── client/ # Prism Central client handling 145 | │ ├── codegen/ # Code generation utilities 146 | │ └── json/ # JSON helpers 147 | ├── pkg/ # components 148 | │ ├── prompts/ # MCP prompt implementations 149 | │ ├── resources/ # Resource handlers 150 | │ └── tools/ # Tool handlers 151 | └── Makefile # Build and utility commands 152 | ``` 153 | 154 | ### Code Generation 155 | 156 | The project uses code generation to create resource and tool handlers. To update these: 157 | 158 | ```bash 159 | make generate 160 | ``` 161 | 162 | ## Limitations 163 | 164 | - Response size is limited by the MCP protocol 165 | - Some resources with large response sizes may cause errors 166 | - No pagination support in the current implementation 167 | - Only supports read operations, no create/update/delete 168 | 169 | ## License 170 | 171 | This project is licensed under the MIT License - see the LICENSE file for details. 172 | 173 | ## Acknowledgments 174 | 175 | - [Nutanix](https://www.nutanix.com/) for creating the Prism API 176 | - [Mark3Labs](https://github.com/mark3labs) for the MCP Go library 177 | - [Nutanix Cloud Native](https://github.com/nutanix-cloud-native) for the Prism Go Client 178 | 179 | ## Contributing 180 | 181 | This is an experimental project with no formal contribution process. Feel free to create issues or pull requests. 182 | ``` -------------------------------------------------------------------------------- /pkg/resources/vm.go: -------------------------------------------------------------------------------- ```go 1 | package resources 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | 8 | "github.com/mark3labs/mcp-go/mcp" 9 | "github.com/mark3labs/mcp-go/server" 10 | ) 11 | 12 | // VM defines the VM resource template 13 | func VM() mcp.ResourceTemplate { 14 | return mcp.NewResourceTemplate( 15 | string(ResourceURIPrefix(ResourceTypeVM))+"{uuid}", 16 | string(ResourceTypeVM), 17 | mcp.WithTemplateDescription("Virtual Machine resource"), 18 | mcp.WithTemplateMIMEType("application/json"), 19 | ) 20 | } 21 | 22 | // VMHandler implements the handler for the VM resource 23 | func VMHandler() server.ResourceTemplateHandlerFunc { 24 | return CreateResourceHandler(ResourceTypeVM, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { 25 | // Get the VM 26 | return client.V3().GetVM(ctx, uuid) 27 | }) 28 | } 29 | ``` -------------------------------------------------------------------------------- /pkg/resources/host.go: -------------------------------------------------------------------------------- ```go 1 | package resources 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | 8 | "github.com/mark3labs/mcp-go/mcp" 9 | "github.com/mark3labs/mcp-go/server" 10 | ) 11 | 12 | // Host defines the Host resource template 13 | func Host() mcp.ResourceTemplate { 14 | return mcp.NewResourceTemplate( 15 | string(ResourceURIPrefix(ResourceTypeHost))+"{uuid}", 16 | string(ResourceTypeHost), 17 | mcp.WithTemplateDescription("Host resource"), 18 | mcp.WithTemplateMIMEType("application/json"), 19 | ) 20 | } 21 | 22 | // HostHandler implements the handler for the Host resource 23 | func HostHandler() server.ResourceTemplateHandlerFunc { 24 | return CreateResourceHandler(ResourceTypeHost, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { 25 | // Get the Host 26 | return client.V3().GetHost(ctx, uuid) 27 | }) 28 | } 29 | ``` -------------------------------------------------------------------------------- /pkg/resources/role.go: -------------------------------------------------------------------------------- ```go 1 | package resources 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | 8 | "github.com/mark3labs/mcp-go/mcp" 9 | "github.com/mark3labs/mcp-go/server" 10 | ) 11 | 12 | // Role defines the Role resource template 13 | func Role() mcp.ResourceTemplate { 14 | return mcp.NewResourceTemplate( 15 | string(ResourceURIPrefix(ResourceTypeRole))+"{uuid}", 16 | string(ResourceTypeRole), 17 | mcp.WithTemplateDescription("Role resource"), 18 | mcp.WithTemplateMIMEType("application/json"), 19 | ) 20 | } 21 | 22 | // RoleHandler implements the handler for the Role resource 23 | func RoleHandler() server.ResourceTemplateHandlerFunc { 24 | return CreateResourceHandler(ResourceTypeRole, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { 25 | // Get the Role 26 | return client.V3().GetRole(ctx, uuid) 27 | }) 28 | } 29 | ``` -------------------------------------------------------------------------------- /pkg/resources/user.go: -------------------------------------------------------------------------------- ```go 1 | package resources 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | 8 | "github.com/mark3labs/mcp-go/mcp" 9 | "github.com/mark3labs/mcp-go/server" 10 | ) 11 | 12 | // User defines the User resource template 13 | func User() mcp.ResourceTemplate { 14 | return mcp.NewResourceTemplate( 15 | string(ResourceURIPrefix(ResourceTypeUser))+"{uuid}", 16 | string(ResourceTypeUser), 17 | mcp.WithTemplateDescription("User resource"), 18 | mcp.WithTemplateMIMEType("application/json"), 19 | ) 20 | } 21 | 22 | // UserHandler implements the handler for the User resource 23 | func UserHandler() server.ResourceTemplateHandlerFunc { 24 | return CreateResourceHandler(ResourceTypeUser, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { 25 | // Get the User 26 | return client.V3().GetUser(ctx, uuid) 27 | }) 28 | } 29 | ``` -------------------------------------------------------------------------------- /pkg/resources/image.go: -------------------------------------------------------------------------------- ```go 1 | package resources 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | 8 | "github.com/mark3labs/mcp-go/mcp" 9 | "github.com/mark3labs/mcp-go/server" 10 | ) 11 | 12 | // Image defines the Image resource template 13 | func Image() mcp.ResourceTemplate { 14 | return mcp.NewResourceTemplate( 15 | string(ResourceURIPrefix(ResourceTypeImage))+"{uuid}", 16 | string(ResourceTypeImage), 17 | mcp.WithTemplateDescription("Image resource"), 18 | mcp.WithTemplateMIMEType("application/json"), 19 | ) 20 | } 21 | 22 | // ImageHandler implements the handler for the Image resource 23 | func ImageHandler() server.ResourceTemplateHandlerFunc { 24 | return CreateResourceHandler(ResourceTypeImage, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { 25 | // Get the Image 26 | return client.V3().GetImage(ctx, uuid) 27 | }) 28 | } 29 | ``` -------------------------------------------------------------------------------- /pkg/resources/subnet.go: -------------------------------------------------------------------------------- ```go 1 | package resources 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | 8 | "github.com/mark3labs/mcp-go/mcp" 9 | "github.com/mark3labs/mcp-go/server" 10 | ) 11 | 12 | // Subnet defines the Subnet resource template 13 | func Subnet() mcp.ResourceTemplate { 14 | return mcp.NewResourceTemplate( 15 | string(ResourceURIPrefix(ResourceTypeSubnet))+"{uuid}", 16 | string(ResourceTypeSubnet), 17 | mcp.WithTemplateDescription("Subnet resource"), 18 | mcp.WithTemplateMIMEType("application/json"), 19 | ) 20 | } 21 | 22 | // SubnetHandler implements the handler for the Subnet resource 23 | func SubnetHandler() server.ResourceTemplateHandlerFunc { 24 | return CreateResourceHandler(ResourceTypeSubnet, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { 25 | // Get the Subnet 26 | return client.V3().GetSubnet(ctx, uuid) 27 | }) 28 | } 29 | ``` -------------------------------------------------------------------------------- /pkg/resources/cluster.go: -------------------------------------------------------------------------------- ```go 1 | package resources 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | 8 | "github.com/mark3labs/mcp-go/mcp" 9 | "github.com/mark3labs/mcp-go/server" 10 | ) 11 | 12 | // Cluster defines the Cluster resource template 13 | func Cluster() mcp.ResourceTemplate { 14 | return mcp.NewResourceTemplate( 15 | string(ResourceURIPrefix(ResourceTypeCluster))+"{uuid}", 16 | string(ResourceTypeCluster), 17 | mcp.WithTemplateDescription("Cluster resource"), 18 | mcp.WithTemplateMIMEType("application/json"), 19 | ) 20 | } 21 | 22 | // ClusterHandler implements the handler for the Cluster resource 23 | func ClusterHandler() server.ResourceTemplateHandlerFunc { 24 | return CreateResourceHandler(ResourceTypeCluster, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { 25 | // Get the Cluster 26 | return client.V3().GetCluster(ctx, uuid) 27 | }) 28 | } 29 | ``` -------------------------------------------------------------------------------- /pkg/resources/project.go: -------------------------------------------------------------------------------- ```go 1 | package resources 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | 8 | "github.com/mark3labs/mcp-go/mcp" 9 | "github.com/mark3labs/mcp-go/server" 10 | ) 11 | 12 | // Project defines the Project resource template 13 | func Project() mcp.ResourceTemplate { 14 | return mcp.NewResourceTemplate( 15 | string(ResourceURIPrefix(ResourceTypeProject))+"{uuid}", 16 | string(ResourceTypeProject), 17 | mcp.WithTemplateDescription("Project resource"), 18 | mcp.WithTemplateMIMEType("application/json"), 19 | ) 20 | } 21 | 22 | // ProjectHandler implements the handler for the Project resource 23 | func ProjectHandler() server.ResourceTemplateHandlerFunc { 24 | return CreateResourceHandler(ResourceTypeProject, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { 25 | // Get the Project 26 | return client.V3().GetProject(ctx, uuid) 27 | }) 28 | } 29 | ``` -------------------------------------------------------------------------------- /pkg/resources/category.go: -------------------------------------------------------------------------------- ```go 1 | package resources 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | 8 | "github.com/mark3labs/mcp-go/mcp" 9 | "github.com/mark3labs/mcp-go/server" 10 | ) 11 | 12 | // Category defines the Category resource template 13 | func Category() mcp.ResourceTemplate { 14 | return mcp.NewResourceTemplate( 15 | string(ResourceURIPrefix(ResourceTypeCategory))+"{uuid}", 16 | string(ResourceTypeCategory), 17 | mcp.WithTemplateDescription("Category resource"), 18 | mcp.WithTemplateMIMEType("application/json"), 19 | ) 20 | } 21 | 22 | // CategoryHandler implements the handler for the Category resource 23 | func CategoryHandler() server.ResourceTemplateHandlerFunc { 24 | return CreateResourceHandler(ResourceTypeCategory, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { 25 | // Get the Category 26 | return client.V3().GetCategoryKey(ctx, uuid) 27 | }) 28 | } 29 | ``` -------------------------------------------------------------------------------- /pkg/resources/usergroup.go: -------------------------------------------------------------------------------- ```go 1 | package resources 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | 8 | "github.com/mark3labs/mcp-go/mcp" 9 | "github.com/mark3labs/mcp-go/server" 10 | ) 11 | 12 | // UserGroup defines the UserGroup resource template 13 | func UserGroup() mcp.ResourceTemplate { 14 | return mcp.NewResourceTemplate( 15 | string(ResourceURIPrefix(ResourceTypeUserGroup))+"{uuid}", 16 | string(ResourceTypeUserGroup), 17 | mcp.WithTemplateDescription("User Group resource"), 18 | mcp.WithTemplateMIMEType("application/json"), 19 | ) 20 | } 21 | 22 | // UserGroupHandler implements the handler for the UserGroup resource 23 | func UserGroupHandler() server.ResourceTemplateHandlerFunc { 24 | return CreateResourceHandler(ResourceTypeUserGroup, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { 25 | // Get the UserGroup 26 | return client.V3().GetUserGroup(ctx, uuid) 27 | }) 28 | } 29 | ``` -------------------------------------------------------------------------------- /pkg/resources/permission.go: -------------------------------------------------------------------------------- ```go 1 | package resources 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | 8 | "github.com/mark3labs/mcp-go/mcp" 9 | "github.com/mark3labs/mcp-go/server" 10 | ) 11 | 12 | // Permission defines the Permission resource template 13 | func Permission() mcp.ResourceTemplate { 14 | return mcp.NewResourceTemplate( 15 | string(ResourceURIPrefix(ResourceTypePermission))+"{uuid}", 16 | string(ResourceTypePermission), 17 | mcp.WithTemplateDescription("Permission resource"), 18 | mcp.WithTemplateMIMEType("application/json"), 19 | ) 20 | } 21 | 22 | // PermissionHandler implements the handler for the Permission resource 23 | func PermissionHandler() server.ResourceTemplateHandlerFunc { 24 | return CreateResourceHandler(ResourceTypePermission, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { 25 | // Get the Permission 26 | return client.V3().GetPermission(ctx, uuid) 27 | }) 28 | } 29 | ``` -------------------------------------------------------------------------------- /pkg/resources/volumegroup.go: -------------------------------------------------------------------------------- ```go 1 | package resources 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | 8 | "github.com/mark3labs/mcp-go/mcp" 9 | "github.com/mark3labs/mcp-go/server" 10 | ) 11 | 12 | // VolumeGroup defines the VolumeGroup resource template 13 | func VolumeGroup() mcp.ResourceTemplate { 14 | return mcp.NewResourceTemplate( 15 | string(ResourceURIPrefix(ResourceTypeVolumeGroup))+"{uuid}", 16 | string(ResourceTypeVolumeGroup), 17 | mcp.WithTemplateDescription("Volume Group resource"), 18 | mcp.WithTemplateMIMEType("application/json"), 19 | ) 20 | } 21 | 22 | // VolumeGroupHandler implements the handler for the VolumeGroup resource 23 | func VolumeGroupHandler() server.ResourceTemplateHandlerFunc { 24 | return CreateResourceHandler(ResourceTypeVolumeGroup, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { 25 | // Get the VolumeGroup 26 | return client.V3().GetVolumeGroup(ctx, uuid) 27 | }) 28 | } 29 | ``` -------------------------------------------------------------------------------- /pkg/resources/addressgroup.go: -------------------------------------------------------------------------------- ```go 1 | package resources 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | 8 | "github.com/mark3labs/mcp-go/mcp" 9 | "github.com/mark3labs/mcp-go/server" 10 | ) 11 | 12 | // AddressGroup defines the AddressGroup resource template 13 | func AddressGroup() mcp.ResourceTemplate { 14 | return mcp.NewResourceTemplate( 15 | string(ResourceURIPrefix(ResourceTypeAddressGroup))+"{uuid}", 16 | string(ResourceTypeAddressGroup), 17 | mcp.WithTemplateDescription("Address Group resource"), 18 | mcp.WithTemplateMIMEType("application/json"), 19 | ) 20 | } 21 | 22 | // AddressGroupHandler implements the handler for the AddressGroup resource 23 | func AddressGroupHandler() server.ResourceTemplateHandlerFunc { 24 | return CreateResourceHandler(ResourceTypeAddressGroup, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { 25 | // Get the AddressGroup 26 | return client.V3().GetAddressGroup(ctx, uuid) 27 | }) 28 | } 29 | ``` -------------------------------------------------------------------------------- /pkg/resources/recoveryplan.go: -------------------------------------------------------------------------------- ```go 1 | package resources 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | 8 | "github.com/mark3labs/mcp-go/mcp" 9 | "github.com/mark3labs/mcp-go/server" 10 | ) 11 | 12 | // RecoveryPlan defines the RecoveryPlan resource template 13 | func RecoveryPlan() mcp.ResourceTemplate { 14 | return mcp.NewResourceTemplate( 15 | string(ResourceURIPrefix(ResourceTypeRecoveryPlan))+"{uuid}", 16 | string(ResourceTypeRecoveryPlan), 17 | mcp.WithTemplateDescription("Recovery Plan resource"), 18 | mcp.WithTemplateMIMEType("application/json"), 19 | ) 20 | } 21 | 22 | // RecoveryPlanHandler implements the handler for the RecoveryPlan resource 23 | func RecoveryPlanHandler() server.ResourceTemplateHandlerFunc { 24 | return CreateResourceHandler(ResourceTypeRecoveryPlan, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { 25 | // Get the RecoveryPlan 26 | return client.V3().GetRecoveryPlan(ctx, uuid) 27 | }) 28 | } 29 | ``` -------------------------------------------------------------------------------- /pkg/resources/servicegroup.go: -------------------------------------------------------------------------------- ```go 1 | package resources 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | 8 | "github.com/mark3labs/mcp-go/mcp" 9 | "github.com/mark3labs/mcp-go/server" 10 | ) 11 | 12 | // ServiceGroup defines the ServiceGroup resource template 13 | func ServiceGroup() mcp.ResourceTemplate { 14 | return mcp.NewResourceTemplate( 15 | string(ResourceURIPrefix(ResourceTypeServiceGroup))+"{uuid}", 16 | string(ResourceTypeServiceGroup), 17 | mcp.WithTemplateDescription("Service Group resource"), 18 | mcp.WithTemplateMIMEType("application/json"), 19 | ) 20 | } 21 | 22 | // ServiceGroupHandler implements the handler for the ServiceGroup resource 23 | func ServiceGroupHandler() server.ResourceTemplateHandlerFunc { 24 | return CreateResourceHandler(ResourceTypeServiceGroup, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { 25 | // Get the ServiceGroup 26 | return client.V3().GetServiceGroup(ctx, uuid) 27 | }) 28 | } 29 | ``` -------------------------------------------------------------------------------- /internal/codegen/cmd/main.go: -------------------------------------------------------------------------------- ```go 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | 9 | "github.com/thunderboltsid/mcp-nutanix/internal/codegen/templates" 10 | ) 11 | 12 | func main() { 13 | // Parse command-line arguments 14 | outputDir := flag.String("output", ".", "Output directory for generated files") 15 | flag.Parse() 16 | 17 | // Get absolute path 18 | absPath, err := filepath.Abs(*outputDir) 19 | if err != nil { 20 | fmt.Printf("Error getting absolute path: %v\n", err) 21 | os.Exit(1) 22 | } 23 | 24 | fmt.Printf("Generating resource and tool files in: %s\n", absPath) 25 | 26 | // Generate all resource files 27 | if err := templates.GenerateResourceFiles(absPath); err != nil { 28 | fmt.Printf("Error generating files: %v\n", err) 29 | os.Exit(1) 30 | } 31 | 32 | // Generate all resource files 33 | if err := templates.GenerateToolFiles(absPath); err != nil { 34 | fmt.Printf("Error generating files: %v\n", err) 35 | os.Exit(1) 36 | } 37 | 38 | fmt.Println("Resource and tool files generated successfully!") 39 | } 40 | ``` -------------------------------------------------------------------------------- /pkg/resources/protectionrule.go: -------------------------------------------------------------------------------- ```go 1 | package resources 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | 8 | "github.com/mark3labs/mcp-go/mcp" 9 | "github.com/mark3labs/mcp-go/server" 10 | ) 11 | 12 | // ProtectionRule defines the ProtectionRule resource template 13 | func ProtectionRule() mcp.ResourceTemplate { 14 | return mcp.NewResourceTemplate( 15 | string(ResourceURIPrefix(ResourceTypeProtectionRule))+"{uuid}", 16 | string(ResourceTypeProtectionRule), 17 | mcp.WithTemplateDescription("Protection Rule resource"), 18 | mcp.WithTemplateMIMEType("application/json"), 19 | ) 20 | } 21 | 22 | // ProtectionRuleHandler implements the handler for the ProtectionRule resource 23 | func ProtectionRuleHandler() server.ResourceTemplateHandlerFunc { 24 | return CreateResourceHandler(ResourceTypeProtectionRule, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { 25 | // Get the ProtectionRule 26 | return client.V3().GetProtectionRule(ctx, uuid) 27 | }) 28 | } 29 | ``` -------------------------------------------------------------------------------- /pkg/resources/recoveryplanjob.go: -------------------------------------------------------------------------------- ```go 1 | package resources 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | 8 | "github.com/mark3labs/mcp-go/mcp" 9 | "github.com/mark3labs/mcp-go/server" 10 | ) 11 | 12 | // RecoveryPlanJob defines the RecoveryPlanJob resource template 13 | func RecoveryPlanJob() mcp.ResourceTemplate { 14 | return mcp.NewResourceTemplate( 15 | string(ResourceURIPrefix(ResourceTypeRecoveryPlanJob))+"{uuid}", 16 | string(ResourceTypeRecoveryPlanJob), 17 | mcp.WithTemplateDescription("Recovery Plan Job resource"), 18 | mcp.WithTemplateMIMEType("application/json"), 19 | ) 20 | } 21 | 22 | // RecoveryPlanJobHandler implements the handler for the RecoveryPlanJob resource 23 | func RecoveryPlanJobHandler() server.ResourceTemplateHandlerFunc { 24 | return CreateResourceHandler(ResourceTypeRecoveryPlanJob, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { 25 | // Get the RecoveryPlanJob 26 | return client.V3().GetRecoveryPlanJob(ctx, uuid) 27 | }) 28 | } 29 | ``` -------------------------------------------------------------------------------- /pkg/resources/availabilityzone.go: -------------------------------------------------------------------------------- ```go 1 | package resources 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | 8 | "github.com/mark3labs/mcp-go/mcp" 9 | "github.com/mark3labs/mcp-go/server" 10 | ) 11 | 12 | // AvailabilityZone defines the AvailabilityZone resource template 13 | func AvailabilityZone() mcp.ResourceTemplate { 14 | return mcp.NewResourceTemplate( 15 | string(ResourceURIPrefix(ResourceTypeAvailabilityZone))+"{uuid}", 16 | string(ResourceTypeAvailabilityZone), 17 | mcp.WithTemplateDescription("Availability Zone resource"), 18 | mcp.WithTemplateMIMEType("application/json"), 19 | ) 20 | } 21 | 22 | // AvailabilityZoneHandler implements the handler for the AvailabilityZone resource 23 | func AvailabilityZoneHandler() server.ResourceTemplateHandlerFunc { 24 | return CreateResourceHandler(ResourceTypeAvailabilityZone, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { 25 | // Get the AvailabilityZone 26 | return client.V3().GetAvailabilityZone(ctx, uuid) 27 | }) 28 | } 29 | ``` -------------------------------------------------------------------------------- /pkg/resources/accesscontrolpolicy.go: -------------------------------------------------------------------------------- ```go 1 | package resources 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | 8 | "github.com/mark3labs/mcp-go/mcp" 9 | "github.com/mark3labs/mcp-go/server" 10 | ) 11 | 12 | // AccessControlPolicy defines the AccessControlPolicy resource template 13 | func AccessControlPolicy() mcp.ResourceTemplate { 14 | return mcp.NewResourceTemplate( 15 | string(ResourceURIPrefix(ResourceTypeAccessControlPolicy))+"{uuid}", 16 | string(ResourceTypeAccessControlPolicy), 17 | mcp.WithTemplateDescription("Access Control Policy resource"), 18 | mcp.WithTemplateMIMEType("application/json"), 19 | ) 20 | } 21 | 22 | // AccessControlPolicyHandler implements the handler for the AccessControlPolicy resource 23 | func AccessControlPolicyHandler() server.ResourceTemplateHandlerFunc { 24 | return CreateResourceHandler(ResourceTypeAccessControlPolicy, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { 25 | // Get the AccessControlPolicy 26 | return client.V3().GetAccessControlPolicy(ctx, uuid) 27 | }) 28 | } 29 | ``` -------------------------------------------------------------------------------- /pkg/resources/networksecurityrule.go: -------------------------------------------------------------------------------- ```go 1 | package resources 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | 8 | "github.com/mark3labs/mcp-go/mcp" 9 | "github.com/mark3labs/mcp-go/server" 10 | ) 11 | 12 | // NetworkSecurityRule defines the NetworkSecurityRule resource template 13 | func NetworkSecurityRule() mcp.ResourceTemplate { 14 | return mcp.NewResourceTemplate( 15 | string(ResourceURIPrefix(ResourceTypeNetworkSecurityRule))+"{uuid}", 16 | string(ResourceTypeNetworkSecurityRule), 17 | mcp.WithTemplateDescription("Network Security Rule resource"), 18 | mcp.WithTemplateMIMEType("application/json"), 19 | ) 20 | } 21 | 22 | // NetworkSecurityRuleHandler implements the handler for the NetworkSecurityRule resource 23 | func NetworkSecurityRuleHandler() server.ResourceTemplateHandlerFunc { 24 | return CreateResourceHandler(ResourceTypeNetworkSecurityRule, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { 25 | // Get the NetworkSecurityRule 26 | return client.V3().GetNetworkSecurityRule(ctx, uuid) 27 | }) 28 | } 29 | ``` -------------------------------------------------------------------------------- /pkg/tools/apinamespace.go: -------------------------------------------------------------------------------- ```go 1 | package tools 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | "github.com/thunderboltsid/mcp-nutanix/internal/json" 8 | 9 | "github.com/mark3labs/mcp-go/mcp" 10 | "github.com/mark3labs/mcp-go/server" 11 | ) 12 | 13 | // ApiNamespacesList defines the API namespaces list tool 14 | func ApiNamespacesList() mcp.Tool { 15 | return mcp.NewTool("api_namespaces_list", 16 | mcp.WithDescription("List available API namespaces and their routes in Prism Central"), 17 | ) 18 | } 19 | 20 | // ApiNamespacesListHandler implements the handler for the API namespaces list tool 21 | func ApiNamespacesListHandler() server.ToolHandlerFunc { 22 | return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 23 | // Get the Prism client 24 | prismClient := client.GetPrismClient() 25 | 26 | // Call the Actuator API to get version routes 27 | response, err := prismClient.V4().ActuatorApiInstance.GetVersionRoutes(ctx) 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | // Convert to JSON using regular JSON encoder 33 | cjson := json.RegularJSONEncoder(response) 34 | jsonBytes, err := cjson.MarshalJSON() 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | return mcp.NewToolResultText(string(jsonBytes)), nil 40 | } 41 | } 42 | ``` -------------------------------------------------------------------------------- /internal/client/modelcontextclient.go: -------------------------------------------------------------------------------- ```go 1 | package client 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | 7 | "github.com/nutanix-cloud-native/prism-go-client/environment/providers/mcp" 8 | ) 9 | 10 | // mcpModelContextClient is an example implementation of ModelContextClient. 11 | // It stores key–value pairs in an in-memory map. 12 | type mcpModelContextClient struct { 13 | mu sync.RWMutex 14 | data map[string]string 15 | } 16 | 17 | var PrismClientProvider = &mcpModelContextClient{ 18 | data: make(map[string]string), 19 | } 20 | 21 | var _ mcp.ModelContextClient = &mcpModelContextClient{} 22 | 23 | // NewMCPModelContextClient creates a new instance with the provided initial data. 24 | func NewMCPModelContextClient(initialData map[string]string) mcp.ModelContextClient { 25 | if initialData == nil { 26 | initialData = make(map[string]string) 27 | } 28 | 29 | return &mcpModelContextClient{ 30 | data: initialData, 31 | } 32 | } 33 | 34 | // GetValue retrieves the value for a given key from the model context. 35 | // It returns an error if the key is not found. 36 | func (c *mcpModelContextClient) GetValue(key string) (string, error) { 37 | c.mu.RLock() 38 | defer c.mu.RUnlock() 39 | if val, exists := c.data[key]; exists { 40 | return val, nil 41 | } 42 | return "", errors.New("model context key not found") 43 | } 44 | 45 | // Optionally, you can add a method to update values in the model context. 46 | func (c *mcpModelContextClient) UpdateValue(key, value string) { 47 | c.mu.Lock() 48 | defer c.mu.Unlock() 49 | c.data[key] = value 50 | } 51 | ``` -------------------------------------------------------------------------------- /internal/json/marshal_test.go: -------------------------------------------------------------------------------- ```go 1 | package json 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestMarshalJSON(t *testing.T) { 10 | jsonData := []byte(` 11 | { 12 | "api_version": "3.0", 13 | "entities": [ 14 | { 15 | "metadata": {"uuid": "b7816400-16c5-47c7-9fcc-474e39594ad5"}, 16 | "status": {"big": "object"}, 17 | "spec": { 18 | "name": "vm1", 19 | "resources": { 20 | "guest_customization": {"very": "large", "nested": "object"}, 21 | "subnets": [{"name": "subnet1"}, {"name": "subnet2"}] 22 | } 23 | } 24 | }, 25 | { 26 | "metadata": {"uuid": "b7816400-16c5-47c7-9fcc-474e39594ad6"}, 27 | "status": {"big": "object2"}, 28 | "spec": { 29 | "name": "vm2", 30 | "resources": { 31 | "guest_customization": {"very": "large", "nested": "object"}, 32 | "subnets": [{"name": "subnet1"}, {"name": "subnet2"}] 33 | } 34 | } 35 | } 36 | ] 37 | }`) 38 | ujson := make(map[string]any) 39 | err := json.Unmarshal(jsonData, &ujson) 40 | assert.NoError(t, err) 41 | 42 | cjson := CustomJSON{ 43 | Value: ujson, 44 | StripPaths: []string{ 45 | "api_version", 46 | "entities[].status", 47 | "spec.resources.guest_customization", 48 | "entities[].spec.resources.guest_customization", 49 | }, 50 | } 51 | 52 | mdata, err := json.Marshal(cjson) 53 | assert.NoError(t, err) 54 | assert.NotNil(t, mdata) 55 | } 56 | ``` -------------------------------------------------------------------------------- /pkg/tools/vm.go: -------------------------------------------------------------------------------- ```go 1 | package tools 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | "github.com/thunderboltsid/mcp-nutanix/pkg/resources" 8 | 9 | "github.com/mark3labs/mcp-go/mcp" 10 | "github.com/mark3labs/mcp-go/server" 11 | ) 12 | 13 | // VM defines the VM tool 14 | func VMList() mcp.Tool { 15 | return mcp.NewTool("vm_list", 16 | mcp.WithDescription("List vm resources"), 17 | mcp.WithString("filter", 18 | mcp.Description("Optional text filter (interpreted by LLM)"), 19 | ), 20 | ) 21 | } 22 | 23 | // VMListHandler implements the handler for the VM list tool 24 | func VMListHandler() server.ToolHandlerFunc { 25 | return CreateListToolHandler( 26 | resources.ResourceTypeVM, 27 | // Define the ListResourceFunc implementation 28 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 29 | 30 | // Use ListAll function to get all resources 31 | return client.V3().ListAllVM(ctx, "") 32 | 33 | }, 34 | ) 35 | } 36 | 37 | // VMCount defines the VM count tool 38 | func VMCount() mcp.Tool { 39 | return mcp.NewTool("vm_count", 40 | mcp.WithDescription("Count vm resources"), 41 | mcp.WithString("filter", 42 | mcp.Description("Optional text filter (interpreted by LLM)"), 43 | ), 44 | ) 45 | } 46 | 47 | // VMCountHandler implements the handler for the VM count tool 48 | func VMCountHandler() server.ToolHandlerFunc { 49 | return CreateCountToolHandler( 50 | resources.ResourceTypeVM, 51 | // Define the ListResourceFunc implementation 52 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 53 | 54 | // Use ListAll function to get all resources 55 | resp, err := client.V3().ListAllVM(ctx, "") 56 | 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | res := map[string]interface{}{ 62 | "resource_type": "VM", 63 | "count": len(resp.Entities), 64 | "metadata": resp.Metadata, 65 | } 66 | 67 | return res, nil 68 | }, 69 | ) 70 | } 71 | ``` -------------------------------------------------------------------------------- /pkg/tools/role.go: -------------------------------------------------------------------------------- ```go 1 | package tools 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | "github.com/thunderboltsid/mcp-nutanix/pkg/resources" 8 | 9 | "github.com/mark3labs/mcp-go/mcp" 10 | "github.com/mark3labs/mcp-go/server" 11 | ) 12 | 13 | // Role defines the Role tool 14 | func RoleList() mcp.Tool { 15 | return mcp.NewTool("role_list", 16 | mcp.WithDescription("List role resources"), 17 | mcp.WithString("filter", 18 | mcp.Description("Optional text filter (interpreted by LLM)"), 19 | ), 20 | ) 21 | } 22 | 23 | // RoleListHandler implements the handler for the Role list tool 24 | func RoleListHandler() server.ToolHandlerFunc { 25 | return CreateListToolHandler( 26 | resources.ResourceTypeRole, 27 | // Define the ListResourceFunc implementation 28 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 29 | 30 | // Use ListAll function to get all resources 31 | return client.V3().ListAllRole(ctx, "") 32 | 33 | }, 34 | ) 35 | } 36 | 37 | // RoleCount defines the Role count tool 38 | func RoleCount() mcp.Tool { 39 | return mcp.NewTool("role_count", 40 | mcp.WithDescription("Count role resources"), 41 | mcp.WithString("filter", 42 | mcp.Description("Optional text filter (interpreted by LLM)"), 43 | ), 44 | ) 45 | } 46 | 47 | // RoleCountHandler implements the handler for the Role count tool 48 | func RoleCountHandler() server.ToolHandlerFunc { 49 | return CreateCountToolHandler( 50 | resources.ResourceTypeRole, 51 | // Define the ListResourceFunc implementation 52 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 53 | 54 | // Use ListAll function to get all resources 55 | resp, err := client.V3().ListAllRole(ctx, "") 56 | 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | res := map[string]interface{}{ 62 | "resource_type": "Role", 63 | "count": len(resp.Entities), 64 | "metadata": resp.Metadata, 65 | } 66 | 67 | return res, nil 68 | }, 69 | ) 70 | } 71 | ``` -------------------------------------------------------------------------------- /pkg/tools/user.go: -------------------------------------------------------------------------------- ```go 1 | package tools 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | "github.com/thunderboltsid/mcp-nutanix/pkg/resources" 8 | 9 | "github.com/mark3labs/mcp-go/mcp" 10 | "github.com/mark3labs/mcp-go/server" 11 | ) 12 | 13 | // User defines the User tool 14 | func UserList() mcp.Tool { 15 | return mcp.NewTool("user_list", 16 | mcp.WithDescription("List user resources"), 17 | mcp.WithString("filter", 18 | mcp.Description("Optional text filter (interpreted by LLM)"), 19 | ), 20 | ) 21 | } 22 | 23 | // UserListHandler implements the handler for the User list tool 24 | func UserListHandler() server.ToolHandlerFunc { 25 | return CreateListToolHandler( 26 | resources.ResourceTypeUser, 27 | // Define the ListResourceFunc implementation 28 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 29 | 30 | // Use ListAll function to get all resources 31 | return client.V3().ListAllUser(ctx, "") 32 | 33 | }, 34 | ) 35 | } 36 | 37 | // UserCount defines the User count tool 38 | func UserCount() mcp.Tool { 39 | return mcp.NewTool("user_count", 40 | mcp.WithDescription("Count user resources"), 41 | mcp.WithString("filter", 42 | mcp.Description("Optional text filter (interpreted by LLM)"), 43 | ), 44 | ) 45 | } 46 | 47 | // UserCountHandler implements the handler for the User count tool 48 | func UserCountHandler() server.ToolHandlerFunc { 49 | return CreateCountToolHandler( 50 | resources.ResourceTypeUser, 51 | // Define the ListResourceFunc implementation 52 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 53 | 54 | // Use ListAll function to get all resources 55 | resp, err := client.V3().ListAllUser(ctx, "") 56 | 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | res := map[string]interface{}{ 62 | "resource_type": "User", 63 | "count": len(resp.Entities), 64 | "metadata": resp.Metadata, 65 | } 66 | 67 | return res, nil 68 | }, 69 | ) 70 | } 71 | ``` -------------------------------------------------------------------------------- /pkg/tools/host.go: -------------------------------------------------------------------------------- ```go 1 | package tools 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | "github.com/thunderboltsid/mcp-nutanix/pkg/resources" 8 | 9 | "github.com/mark3labs/mcp-go/mcp" 10 | "github.com/mark3labs/mcp-go/server" 11 | ) 12 | 13 | // Host defines the Host tool 14 | func HostList() mcp.Tool { 15 | return mcp.NewTool("host_list", 16 | mcp.WithDescription("List host resources"), 17 | mcp.WithString("filter", 18 | mcp.Description("Optional text filter (interpreted by LLM)"), 19 | ), 20 | ) 21 | } 22 | 23 | // HostListHandler implements the handler for the Host list tool 24 | func HostListHandler() server.ToolHandlerFunc { 25 | return CreateListToolHandler( 26 | resources.ResourceTypeHost, 27 | // Define the ListResourceFunc implementation 28 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 29 | 30 | // Special case for Host which doesn't take a filter 31 | return client.V3().ListAllHost(ctx) 32 | 33 | }, 34 | ) 35 | } 36 | 37 | // HostCount defines the Host count tool 38 | func HostCount() mcp.Tool { 39 | return mcp.NewTool("host_count", 40 | mcp.WithDescription("Count host resources"), 41 | mcp.WithString("filter", 42 | mcp.Description("Optional text filter (interpreted by LLM)"), 43 | ), 44 | ) 45 | } 46 | 47 | // HostCountHandler implements the handler for the Host count tool 48 | func HostCountHandler() server.ToolHandlerFunc { 49 | return CreateCountToolHandler( 50 | resources.ResourceTypeHost, 51 | // Define the ListResourceFunc implementation 52 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 53 | 54 | // Special case for Host which doesn't take a filter 55 | resp, err := client.V3().ListAllHost(ctx) 56 | 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | res := map[string]interface{}{ 62 | "resource_type": "Host", 63 | "count": len(resp.Entities), 64 | "metadata": resp.Metadata, 65 | } 66 | 67 | return res, nil 68 | }, 69 | ) 70 | } 71 | ``` -------------------------------------------------------------------------------- /pkg/tools/image.go: -------------------------------------------------------------------------------- ```go 1 | package tools 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | "github.com/thunderboltsid/mcp-nutanix/pkg/resources" 8 | 9 | "github.com/mark3labs/mcp-go/mcp" 10 | "github.com/mark3labs/mcp-go/server" 11 | ) 12 | 13 | // Image defines the Image tool 14 | func ImageList() mcp.Tool { 15 | return mcp.NewTool("image_list", 16 | mcp.WithDescription("List image resources"), 17 | mcp.WithString("filter", 18 | mcp.Description("Optional text filter (interpreted by LLM)"), 19 | ), 20 | ) 21 | } 22 | 23 | // ImageListHandler implements the handler for the Image list tool 24 | func ImageListHandler() server.ToolHandlerFunc { 25 | return CreateListToolHandler( 26 | resources.ResourceTypeImage, 27 | // Define the ListResourceFunc implementation 28 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 29 | 30 | // Use ListAll function to get all resources 31 | return client.V3().ListAllImage(ctx, "") 32 | 33 | }, 34 | ) 35 | } 36 | 37 | // ImageCount defines the Image count tool 38 | func ImageCount() mcp.Tool { 39 | return mcp.NewTool("image_count", 40 | mcp.WithDescription("Count image resources"), 41 | mcp.WithString("filter", 42 | mcp.Description("Optional text filter (interpreted by LLM)"), 43 | ), 44 | ) 45 | } 46 | 47 | // ImageCountHandler implements the handler for the Image count tool 48 | func ImageCountHandler() server.ToolHandlerFunc { 49 | return CreateCountToolHandler( 50 | resources.ResourceTypeImage, 51 | // Define the ListResourceFunc implementation 52 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 53 | 54 | // Use ListAll function to get all resources 55 | resp, err := client.V3().ListAllImage(ctx, "") 56 | 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | res := map[string]interface{}{ 62 | "resource_type": "Image", 63 | "count": len(resp.Entities), 64 | "metadata": resp.Metadata, 65 | } 66 | 67 | return res, nil 68 | }, 69 | ) 70 | } 71 | ``` -------------------------------------------------------------------------------- /pkg/tools/cluster.go: -------------------------------------------------------------------------------- ```go 1 | package tools 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | "github.com/thunderboltsid/mcp-nutanix/pkg/resources" 8 | 9 | "github.com/mark3labs/mcp-go/mcp" 10 | "github.com/mark3labs/mcp-go/server" 11 | ) 12 | 13 | // Cluster defines the Cluster tool 14 | func ClusterList() mcp.Tool { 15 | return mcp.NewTool("cluster_list", 16 | mcp.WithDescription("List cluster resources"), 17 | mcp.WithString("filter", 18 | mcp.Description("Optional text filter (interpreted by LLM)"), 19 | ), 20 | ) 21 | } 22 | 23 | // ClusterListHandler implements the handler for the Cluster list tool 24 | func ClusterListHandler() server.ToolHandlerFunc { 25 | return CreateListToolHandler( 26 | resources.ResourceTypeCluster, 27 | // Define the ListResourceFunc implementation 28 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 29 | 30 | // Use ListAll function to get all resources 31 | return client.V3().ListAllCluster(ctx, "") 32 | 33 | }, 34 | ) 35 | } 36 | 37 | // ClusterCount defines the Cluster count tool 38 | func ClusterCount() mcp.Tool { 39 | return mcp.NewTool("cluster_count", 40 | mcp.WithDescription("Count cluster resources"), 41 | mcp.WithString("filter", 42 | mcp.Description("Optional text filter (interpreted by LLM)"), 43 | ), 44 | ) 45 | } 46 | 47 | // ClusterCountHandler implements the handler for the Cluster count tool 48 | func ClusterCountHandler() server.ToolHandlerFunc { 49 | return CreateCountToolHandler( 50 | resources.ResourceTypeCluster, 51 | // Define the ListResourceFunc implementation 52 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 53 | 54 | // Use ListAll function to get all resources 55 | resp, err := client.V3().ListAllCluster(ctx, "") 56 | 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | res := map[string]interface{}{ 62 | "resource_type": "Cluster", 63 | "count": len(resp.Entities), 64 | "metadata": resp.Metadata, 65 | } 66 | 67 | return res, nil 68 | }, 69 | ) 70 | } 71 | ``` -------------------------------------------------------------------------------- /pkg/tools/project.go: -------------------------------------------------------------------------------- ```go 1 | package tools 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | "github.com/thunderboltsid/mcp-nutanix/pkg/resources" 8 | 9 | "github.com/mark3labs/mcp-go/mcp" 10 | "github.com/mark3labs/mcp-go/server" 11 | ) 12 | 13 | // Project defines the Project tool 14 | func ProjectList() mcp.Tool { 15 | return mcp.NewTool("project_list", 16 | mcp.WithDescription("List project resources"), 17 | mcp.WithString("filter", 18 | mcp.Description("Optional text filter (interpreted by LLM)"), 19 | ), 20 | ) 21 | } 22 | 23 | // ProjectListHandler implements the handler for the Project list tool 24 | func ProjectListHandler() server.ToolHandlerFunc { 25 | return CreateListToolHandler( 26 | resources.ResourceTypeProject, 27 | // Define the ListResourceFunc implementation 28 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 29 | 30 | // Use ListAll function to get all resources 31 | return client.V3().ListAllProject(ctx, "") 32 | 33 | }, 34 | ) 35 | } 36 | 37 | // ProjectCount defines the Project count tool 38 | func ProjectCount() mcp.Tool { 39 | return mcp.NewTool("project_count", 40 | mcp.WithDescription("Count project resources"), 41 | mcp.WithString("filter", 42 | mcp.Description("Optional text filter (interpreted by LLM)"), 43 | ), 44 | ) 45 | } 46 | 47 | // ProjectCountHandler implements the handler for the Project count tool 48 | func ProjectCountHandler() server.ToolHandlerFunc { 49 | return CreateCountToolHandler( 50 | resources.ResourceTypeProject, 51 | // Define the ListResourceFunc implementation 52 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 53 | 54 | // Use ListAll function to get all resources 55 | resp, err := client.V3().ListAllProject(ctx, "") 56 | 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | res := map[string]interface{}{ 62 | "resource_type": "Project", 63 | "count": len(resp.Entities), 64 | "metadata": resp.Metadata, 65 | } 66 | 67 | return res, nil 68 | }, 69 | ) 70 | } 71 | ``` -------------------------------------------------------------------------------- /pkg/tools/subnet.go: -------------------------------------------------------------------------------- ```go 1 | package tools 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | "github.com/thunderboltsid/mcp-nutanix/pkg/resources" 8 | 9 | "github.com/mark3labs/mcp-go/mcp" 10 | "github.com/mark3labs/mcp-go/server" 11 | ) 12 | 13 | // Subnet defines the Subnet tool 14 | func SubnetList() mcp.Tool { 15 | return mcp.NewTool("subnet_list", 16 | mcp.WithDescription("List subnet resources"), 17 | mcp.WithString("filter", 18 | mcp.Description("Optional text filter (interpreted by LLM)"), 19 | ), 20 | ) 21 | } 22 | 23 | // SubnetListHandler implements the handler for the Subnet list tool 24 | func SubnetListHandler() server.ToolHandlerFunc { 25 | return CreateListToolHandler( 26 | resources.ResourceTypeSubnet, 27 | // Define the ListResourceFunc implementation 28 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 29 | 30 | // Special case for Subnet which has an extra parameter 31 | return client.V3().ListAllSubnet(ctx, "", nil) 32 | 33 | }, 34 | ) 35 | } 36 | 37 | // SubnetCount defines the Subnet count tool 38 | func SubnetCount() mcp.Tool { 39 | return mcp.NewTool("subnet_count", 40 | mcp.WithDescription("Count subnet resources"), 41 | mcp.WithString("filter", 42 | mcp.Description("Optional text filter (interpreted by LLM)"), 43 | ), 44 | ) 45 | } 46 | 47 | // SubnetCountHandler implements the handler for the Subnet count tool 48 | func SubnetCountHandler() server.ToolHandlerFunc { 49 | return CreateCountToolHandler( 50 | resources.ResourceTypeSubnet, 51 | // Define the ListResourceFunc implementation 52 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 53 | 54 | // Special case for Subnet which has an extra parameter 55 | resp, err := client.V3().ListAllSubnet(ctx, "", nil) 56 | 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | res := map[string]interface{}{ 62 | "resource_type": "Subnet", 63 | "count": len(resp.Entities), 64 | "metadata": resp.Metadata, 65 | } 66 | 67 | return res, nil 68 | }, 69 | ) 70 | } 71 | ``` -------------------------------------------------------------------------------- /pkg/tools/usergroup.go: -------------------------------------------------------------------------------- ```go 1 | package tools 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | "github.com/thunderboltsid/mcp-nutanix/pkg/resources" 8 | 9 | "github.com/mark3labs/mcp-go/mcp" 10 | "github.com/mark3labs/mcp-go/server" 11 | ) 12 | 13 | // UserGroup defines the UserGroup tool 14 | func UserGroupList() mcp.Tool { 15 | return mcp.NewTool("usergroup_list", 16 | mcp.WithDescription("List usergroup resources"), 17 | mcp.WithString("filter", 18 | mcp.Description("Optional text filter (interpreted by LLM)"), 19 | ), 20 | ) 21 | } 22 | 23 | // UserGroupListHandler implements the handler for the UserGroup list tool 24 | func UserGroupListHandler() server.ToolHandlerFunc { 25 | return CreateListToolHandler( 26 | resources.ResourceTypeUserGroup, 27 | // Define the ListResourceFunc implementation 28 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 29 | 30 | // Use ListAll function to get all resources 31 | return client.V3().ListAllUserGroup(ctx, "") 32 | 33 | }, 34 | ) 35 | } 36 | 37 | // UserGroupCount defines the UserGroup count tool 38 | func UserGroupCount() mcp.Tool { 39 | return mcp.NewTool("usergroup_count", 40 | mcp.WithDescription("Count usergroup resources"), 41 | mcp.WithString("filter", 42 | mcp.Description("Optional text filter (interpreted by LLM)"), 43 | ), 44 | ) 45 | } 46 | 47 | // UserGroupCountHandler implements the handler for the UserGroup count tool 48 | func UserGroupCountHandler() server.ToolHandlerFunc { 49 | return CreateCountToolHandler( 50 | resources.ResourceTypeUserGroup, 51 | // Define the ListResourceFunc implementation 52 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 53 | 54 | // Use ListAll function to get all resources 55 | resp, err := client.V3().ListAllUserGroup(ctx, "") 56 | 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | res := map[string]interface{}{ 62 | "resource_type": "UserGroup", 63 | "count": len(resp.Entities), 64 | "metadata": resp.Metadata, 65 | } 66 | 67 | return res, nil 68 | }, 69 | ) 70 | } 71 | ``` -------------------------------------------------------------------------------- /pkg/tools/permission.go: -------------------------------------------------------------------------------- ```go 1 | package tools 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | "github.com/thunderboltsid/mcp-nutanix/pkg/resources" 8 | 9 | "github.com/mark3labs/mcp-go/mcp" 10 | "github.com/mark3labs/mcp-go/server" 11 | ) 12 | 13 | // Permission defines the Permission tool 14 | func PermissionList() mcp.Tool { 15 | return mcp.NewTool("permission_list", 16 | mcp.WithDescription("List permission resources"), 17 | mcp.WithString("filter", 18 | mcp.Description("Optional text filter (interpreted by LLM)"), 19 | ), 20 | ) 21 | } 22 | 23 | // PermissionListHandler implements the handler for the Permission list tool 24 | func PermissionListHandler() server.ToolHandlerFunc { 25 | return CreateListToolHandler( 26 | resources.ResourceTypePermission, 27 | // Define the ListResourceFunc implementation 28 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 29 | 30 | // Use ListAll function to get all resources 31 | return client.V3().ListAllPermission(ctx, "") 32 | 33 | }, 34 | ) 35 | } 36 | 37 | // PermissionCount defines the Permission count tool 38 | func PermissionCount() mcp.Tool { 39 | return mcp.NewTool("permission_count", 40 | mcp.WithDescription("Count permission resources"), 41 | mcp.WithString("filter", 42 | mcp.Description("Optional text filter (interpreted by LLM)"), 43 | ), 44 | ) 45 | } 46 | 47 | // PermissionCountHandler implements the handler for the Permission count tool 48 | func PermissionCountHandler() server.ToolHandlerFunc { 49 | return CreateCountToolHandler( 50 | resources.ResourceTypePermission, 51 | // Define the ListResourceFunc implementation 52 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 53 | 54 | // Use ListAll function to get all resources 55 | resp, err := client.V3().ListAllPermission(ctx, "") 56 | 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | res := map[string]interface{}{ 62 | "resource_type": "Permission", 63 | "count": len(resp.Entities), 64 | "metadata": resp.Metadata, 65 | } 66 | 67 | return res, nil 68 | }, 69 | ) 70 | } 71 | ``` -------------------------------------------------------------------------------- /pkg/tools/addressgroup.go: -------------------------------------------------------------------------------- ```go 1 | package tools 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | "github.com/thunderboltsid/mcp-nutanix/pkg/resources" 8 | 9 | "github.com/mark3labs/mcp-go/mcp" 10 | "github.com/mark3labs/mcp-go/server" 11 | ) 12 | 13 | // AddressGroup defines the AddressGroup tool 14 | func AddressGroupList() mcp.Tool { 15 | return mcp.NewTool("addressgroup_list", 16 | mcp.WithDescription("List addressgroup resources"), 17 | mcp.WithString("filter", 18 | mcp.Description("Optional text filter (interpreted by LLM)"), 19 | ), 20 | ) 21 | } 22 | 23 | // AddressGroupListHandler implements the handler for the AddressGroup list tool 24 | func AddressGroupListHandler() server.ToolHandlerFunc { 25 | return CreateListToolHandler( 26 | resources.ResourceTypeAddressGroup, 27 | // Define the ListResourceFunc implementation 28 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 29 | 30 | // Use ListAll function to get all resources 31 | return client.V3().ListAllAddressGroups(ctx, "") 32 | 33 | }, 34 | ) 35 | } 36 | 37 | // AddressGroupCount defines the AddressGroup count tool 38 | func AddressGroupCount() mcp.Tool { 39 | return mcp.NewTool("addressgroup_count", 40 | mcp.WithDescription("Count addressgroup resources"), 41 | mcp.WithString("filter", 42 | mcp.Description("Optional text filter (interpreted by LLM)"), 43 | ), 44 | ) 45 | } 46 | 47 | // AddressGroupCountHandler implements the handler for the AddressGroup count tool 48 | func AddressGroupCountHandler() server.ToolHandlerFunc { 49 | return CreateCountToolHandler( 50 | resources.ResourceTypeAddressGroup, 51 | // Define the ListResourceFunc implementation 52 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 53 | 54 | // Use ListAll function to get all resources 55 | resp, err := client.V3().ListAllAddressGroups(ctx, "") 56 | 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | res := map[string]interface{}{ 62 | "resource_type": "AddressGroup", 63 | "count": len(resp.Entities), 64 | "metadata": resp.Metadata, 65 | } 66 | 67 | return res, nil 68 | }, 69 | ) 70 | } 71 | ``` -------------------------------------------------------------------------------- /pkg/tools/recoveryplan.go: -------------------------------------------------------------------------------- ```go 1 | package tools 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | "github.com/thunderboltsid/mcp-nutanix/pkg/resources" 8 | 9 | "github.com/mark3labs/mcp-go/mcp" 10 | "github.com/mark3labs/mcp-go/server" 11 | ) 12 | 13 | // RecoveryPlan defines the RecoveryPlan tool 14 | func RecoveryPlanList() mcp.Tool { 15 | return mcp.NewTool("recoveryplan_list", 16 | mcp.WithDescription("List recoveryplan resources"), 17 | mcp.WithString("filter", 18 | mcp.Description("Optional text filter (interpreted by LLM)"), 19 | ), 20 | ) 21 | } 22 | 23 | // RecoveryPlanListHandler implements the handler for the RecoveryPlan list tool 24 | func RecoveryPlanListHandler() server.ToolHandlerFunc { 25 | return CreateListToolHandler( 26 | resources.ResourceTypeRecoveryPlan, 27 | // Define the ListResourceFunc implementation 28 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 29 | 30 | // Use ListAll function to get all resources 31 | return client.V3().ListAllRecoveryPlans(ctx, "") 32 | 33 | }, 34 | ) 35 | } 36 | 37 | // RecoveryPlanCount defines the RecoveryPlan count tool 38 | func RecoveryPlanCount() mcp.Tool { 39 | return mcp.NewTool("recoveryplan_count", 40 | mcp.WithDescription("Count recoveryplan resources"), 41 | mcp.WithString("filter", 42 | mcp.Description("Optional text filter (interpreted by LLM)"), 43 | ), 44 | ) 45 | } 46 | 47 | // RecoveryPlanCountHandler implements the handler for the RecoveryPlan count tool 48 | func RecoveryPlanCountHandler() server.ToolHandlerFunc { 49 | return CreateCountToolHandler( 50 | resources.ResourceTypeRecoveryPlan, 51 | // Define the ListResourceFunc implementation 52 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 53 | 54 | // Use ListAll function to get all resources 55 | resp, err := client.V3().ListAllRecoveryPlans(ctx, "") 56 | 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | res := map[string]interface{}{ 62 | "resource_type": "RecoveryPlan", 63 | "count": len(resp.Entities), 64 | "metadata": resp.Metadata, 65 | } 66 | 67 | return res, nil 68 | }, 69 | ) 70 | } 71 | ``` -------------------------------------------------------------------------------- /pkg/tools/servicegroup.go: -------------------------------------------------------------------------------- ```go 1 | package tools 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | "github.com/thunderboltsid/mcp-nutanix/pkg/resources" 8 | 9 | "github.com/mark3labs/mcp-go/mcp" 10 | "github.com/mark3labs/mcp-go/server" 11 | ) 12 | 13 | // ServiceGroup defines the ServiceGroup tool 14 | func ServiceGroupList() mcp.Tool { 15 | return mcp.NewTool("servicegroup_list", 16 | mcp.WithDescription("List servicegroup resources"), 17 | mcp.WithString("filter", 18 | mcp.Description("Optional text filter (interpreted by LLM)"), 19 | ), 20 | ) 21 | } 22 | 23 | // ServiceGroupListHandler implements the handler for the ServiceGroup list tool 24 | func ServiceGroupListHandler() server.ToolHandlerFunc { 25 | return CreateListToolHandler( 26 | resources.ResourceTypeServiceGroup, 27 | // Define the ListResourceFunc implementation 28 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 29 | 30 | // Use ListAll function to get all resources 31 | return client.V3().ListAllServiceGroups(ctx, "") 32 | 33 | }, 34 | ) 35 | } 36 | 37 | // ServiceGroupCount defines the ServiceGroup count tool 38 | func ServiceGroupCount() mcp.Tool { 39 | return mcp.NewTool("servicegroup_count", 40 | mcp.WithDescription("Count servicegroup resources"), 41 | mcp.WithString("filter", 42 | mcp.Description("Optional text filter (interpreted by LLM)"), 43 | ), 44 | ) 45 | } 46 | 47 | // ServiceGroupCountHandler implements the handler for the ServiceGroup count tool 48 | func ServiceGroupCountHandler() server.ToolHandlerFunc { 49 | return CreateCountToolHandler( 50 | resources.ResourceTypeServiceGroup, 51 | // Define the ListResourceFunc implementation 52 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 53 | 54 | // Use ListAll function to get all resources 55 | resp, err := client.V3().ListAllServiceGroups(ctx, "") 56 | 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | res := map[string]interface{}{ 62 | "resource_type": "ServiceGroup", 63 | "count": len(resp.Entities), 64 | "metadata": resp.Metadata, 65 | } 66 | 67 | return res, nil 68 | }, 69 | ) 70 | } 71 | ``` -------------------------------------------------------------------------------- /pkg/tools/protectionrule.go: -------------------------------------------------------------------------------- ```go 1 | package tools 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | "github.com/thunderboltsid/mcp-nutanix/pkg/resources" 8 | 9 | "github.com/mark3labs/mcp-go/mcp" 10 | "github.com/mark3labs/mcp-go/server" 11 | ) 12 | 13 | // ProtectionRule defines the ProtectionRule tool 14 | func ProtectionRuleList() mcp.Tool { 15 | return mcp.NewTool("protectionrule_list", 16 | mcp.WithDescription("List protectionrule resources"), 17 | mcp.WithString("filter", 18 | mcp.Description("Optional text filter (interpreted by LLM)"), 19 | ), 20 | ) 21 | } 22 | 23 | // ProtectionRuleListHandler implements the handler for the ProtectionRule list tool 24 | func ProtectionRuleListHandler() server.ToolHandlerFunc { 25 | return CreateListToolHandler( 26 | resources.ResourceTypeProtectionRule, 27 | // Define the ListResourceFunc implementation 28 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 29 | 30 | // Use ListAll function to get all resources 31 | return client.V3().ListAllProtectionRules(ctx, "") 32 | 33 | }, 34 | ) 35 | } 36 | 37 | // ProtectionRuleCount defines the ProtectionRule count tool 38 | func ProtectionRuleCount() mcp.Tool { 39 | return mcp.NewTool("protectionrule_count", 40 | mcp.WithDescription("Count protectionrule resources"), 41 | mcp.WithString("filter", 42 | mcp.Description("Optional text filter (interpreted by LLM)"), 43 | ), 44 | ) 45 | } 46 | 47 | // ProtectionRuleCountHandler implements the handler for the ProtectionRule count tool 48 | func ProtectionRuleCountHandler() server.ToolHandlerFunc { 49 | return CreateCountToolHandler( 50 | resources.ResourceTypeProtectionRule, 51 | // Define the ListResourceFunc implementation 52 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 53 | 54 | // Use ListAll function to get all resources 55 | resp, err := client.V3().ListAllProtectionRules(ctx, "") 56 | 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | res := map[string]interface{}{ 62 | "resource_type": "ProtectionRule", 63 | "count": len(resp.Entities), 64 | "metadata": resp.Metadata, 65 | } 66 | 67 | return res, nil 68 | }, 69 | ) 70 | } 71 | ``` -------------------------------------------------------------------------------- /pkg/tools/volumegroup.go: -------------------------------------------------------------------------------- ```go 1 | package tools 2 | 3 | import ( 4 | "context" 5 | 6 | v3 "github.com/nutanix-cloud-native/prism-go-client/v3" 7 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 8 | "github.com/thunderboltsid/mcp-nutanix/pkg/resources" 9 | 10 | "github.com/mark3labs/mcp-go/mcp" 11 | "github.com/mark3labs/mcp-go/server" 12 | ) 13 | 14 | // VolumeGroup defines the VolumeGroup tool 15 | func VolumeGroupList() mcp.Tool { 16 | return mcp.NewTool("volumegroup_list", 17 | mcp.WithDescription("List volumegroup resources"), 18 | mcp.WithString("filter", 19 | mcp.Description("Optional text filter (interpreted by LLM)"), 20 | ), 21 | ) 22 | } 23 | 24 | // VolumeGroupListHandler implements the handler for the VolumeGroup list tool 25 | func VolumeGroupListHandler() server.ToolHandlerFunc { 26 | return CreateListToolHandler( 27 | resources.ResourceTypeVolumeGroup, 28 | // Define the ListResourceFunc implementation 29 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 30 | 31 | // Create DSMetadata without filter 32 | metadata := &v3.DSMetadata{} 33 | 34 | return client.V3().ListVolumeGroup(ctx, metadata) 35 | 36 | }, 37 | ) 38 | } 39 | 40 | // VolumeGroupCount defines the VolumeGroup count tool 41 | func VolumeGroupCount() mcp.Tool { 42 | return mcp.NewTool("volumegroup_count", 43 | mcp.WithDescription("Count volumegroup resources"), 44 | mcp.WithString("filter", 45 | mcp.Description("Optional text filter (interpreted by LLM)"), 46 | ), 47 | ) 48 | } 49 | 50 | // VolumeGroupCountHandler implements the handler for the VolumeGroup count tool 51 | func VolumeGroupCountHandler() server.ToolHandlerFunc { 52 | return CreateCountToolHandler( 53 | resources.ResourceTypeVolumeGroup, 54 | // Define the ListResourceFunc implementation 55 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 56 | 57 | // Create DSMetadata without filter 58 | metadata := &v3.DSMetadata{} 59 | 60 | resp, err := client.V3().ListVolumeGroup(ctx, metadata) 61 | 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | res := map[string]interface{}{ 67 | "resource_type": "VolumeGroup", 68 | "count": len(resp.Entities), 69 | "metadata": resp.Metadata, 70 | } 71 | 72 | return res, nil 73 | }, 74 | ) 75 | } 76 | ``` -------------------------------------------------------------------------------- /pkg/tools/category.go: -------------------------------------------------------------------------------- ```go 1 | package tools 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | "github.com/thunderboltsid/mcp-nutanix/pkg/resources" 8 | 9 | "github.com/mark3labs/mcp-go/mcp" 10 | "github.com/mark3labs/mcp-go/server" 11 | v3 "github.com/nutanix-cloud-native/prism-go-client/v3" 12 | ) 13 | 14 | // Category defines the Category tool 15 | func CategoryList() mcp.Tool { 16 | return mcp.NewTool("category_list", 17 | mcp.WithDescription("List category resources"), 18 | mcp.WithString("filter", 19 | mcp.Description("Optional text filter (interpreted by LLM)"), 20 | ), 21 | ) 22 | } 23 | 24 | // CategoryListHandler implements the handler for the Category list tool 25 | func CategoryListHandler() server.ToolHandlerFunc { 26 | return CreateListToolHandler( 27 | resources.ResourceTypeCategory, 28 | // Define the ListResourceFunc implementation 29 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 30 | 31 | // Special case for Category which takes CategoryListMetadata 32 | metadata := &v3.CategoryListMetadata{} 33 | return client.V3().ListCategories(ctx, metadata) 34 | 35 | }, 36 | ) 37 | } 38 | 39 | // CategoryCount defines the Category count tool 40 | func CategoryCount() mcp.Tool { 41 | return mcp.NewTool("category_count", 42 | mcp.WithDescription("Count category resources"), 43 | mcp.WithString("filter", 44 | mcp.Description("Optional text filter (interpreted by LLM)"), 45 | ), 46 | ) 47 | } 48 | 49 | // CategoryCountHandler implements the handler for the Category count tool 50 | func CategoryCountHandler() server.ToolHandlerFunc { 51 | return CreateCountToolHandler( 52 | resources.ResourceTypeCategory, 53 | // Define the ListResourceFunc implementation 54 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 55 | 56 | // Special case for Category which takes CategoryListMetadata 57 | metadata := &v3.CategoryListMetadata{} 58 | resp, err := client.V3().ListCategories(ctx, metadata) 59 | 60 | if err != nil { 61 | return nil, err 62 | } 63 | 64 | res := map[string]interface{}{ 65 | "resource_type": "Category", 66 | "count": len(resp.Entities), 67 | "metadata": resp.Metadata, 68 | } 69 | 70 | return res, nil 71 | }, 72 | ) 73 | } 74 | ``` -------------------------------------------------------------------------------- /internal/client/client.go: -------------------------------------------------------------------------------- ```go 1 | package client 2 | 3 | import ( 4 | "github.com/nutanix-cloud-native/prism-go-client/environment" 5 | "github.com/nutanix-cloud-native/prism-go-client/environment/providers/local" 6 | "github.com/nutanix-cloud-native/prism-go-client/environment/providers/mcp" 7 | envtypes "github.com/nutanix-cloud-native/prism-go-client/environment/types" 8 | prismclientv3 "github.com/nutanix-cloud-native/prism-go-client/v3" 9 | prismclientv4 "github.com/nutanix-cloud-native/prism-go-client/v4" 10 | "k8s.io/klog" 11 | ) 12 | 13 | var ( 14 | prismClient *NutanixClient 15 | ) 16 | 17 | func Init(modelcontextclient mcp.ModelContextClient) { 18 | prismClient = &NutanixClient{ 19 | env: environment.NewEnvironment(local.NewProvider(), mcp.NewProvider(modelcontextclient)), 20 | v3ClientCache: prismclientv3.NewClientCache(), 21 | v4ClientCache: prismclientv4.NewClientCache(), 22 | } 23 | } 24 | 25 | func GetPrismClient() *NutanixClient { 26 | if prismClient == nil { 27 | panic("Prism client not initialized. Call Init() first.") 28 | } 29 | 30 | return prismClient 31 | } 32 | 33 | type NutanixClient struct { 34 | env envtypes.Environment 35 | v3ClientCache *prismclientv3.ClientCache 36 | v4ClientCache *prismclientv4.ClientCache 37 | } 38 | 39 | // GetV3Client returns the v3 client 40 | func (n *NutanixClient) V3() prismclientv3.Service { 41 | c, err := n.v3ClientCache.GetOrCreate(n) 42 | if err != nil { 43 | panic(err) 44 | } 45 | 46 | return c.V3 47 | } 48 | 49 | // GetV4Client returns the v4 client 50 | func (n *NutanixClient) V4() *prismclientv4.Client { 51 | c, err := n.v4ClientCache.GetOrCreate(n) 52 | if err != nil { 53 | panic(err) 54 | } 55 | 56 | return c 57 | } 58 | 59 | // Key returns the constant client name 60 | // This implements the CachedClientParams interface of prism-go-client 61 | func (n *NutanixClient) Key() string { 62 | return "mcp-server" 63 | } 64 | 65 | // ManagementEndpoint returns the management endpoint of the Nutanix cluster 66 | // This implements the CachedClientParams interface of prism-go-client 67 | func (n *NutanixClient) ManagementEndpoint() envtypes.ManagementEndpoint { 68 | mgmtEndpoint, err := n.env.GetManagementEndpoint(envtypes.Topology{}) 69 | if err != nil { 70 | klog.Errorf("failed to get management endpoint: %s", err.Error()) 71 | return envtypes.ManagementEndpoint{} 72 | } 73 | 74 | return *mgmtEndpoint 75 | } 76 | ``` -------------------------------------------------------------------------------- /pkg/tools/accesscontrolpolicy.go: -------------------------------------------------------------------------------- ```go 1 | package tools 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | "github.com/thunderboltsid/mcp-nutanix/pkg/resources" 8 | 9 | "github.com/mark3labs/mcp-go/mcp" 10 | "github.com/mark3labs/mcp-go/server" 11 | ) 12 | 13 | // AccessControlPolicy defines the AccessControlPolicy tool 14 | func AccessControlPolicyList() mcp.Tool { 15 | return mcp.NewTool("accesscontrolpolicy_list", 16 | mcp.WithDescription("List accesscontrolpolicy resources"), 17 | mcp.WithString("filter", 18 | mcp.Description("Optional text filter (interpreted by LLM)"), 19 | ), 20 | ) 21 | } 22 | 23 | // AccessControlPolicyListHandler implements the handler for the AccessControlPolicy list tool 24 | func AccessControlPolicyListHandler() server.ToolHandlerFunc { 25 | return CreateListToolHandler( 26 | resources.ResourceTypeAccessControlPolicy, 27 | // Define the ListResourceFunc implementation 28 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 29 | 30 | // Use ListAll function to get all resources 31 | return client.V3().ListAllAccessControlPolicy(ctx, "") 32 | 33 | }, 34 | ) 35 | } 36 | 37 | // AccessControlPolicyCount defines the AccessControlPolicy count tool 38 | func AccessControlPolicyCount() mcp.Tool { 39 | return mcp.NewTool("accesscontrolpolicy_count", 40 | mcp.WithDescription("Count accesscontrolpolicy resources"), 41 | mcp.WithString("filter", 42 | mcp.Description("Optional text filter (interpreted by LLM)"), 43 | ), 44 | ) 45 | } 46 | 47 | // AccessControlPolicyCountHandler implements the handler for the AccessControlPolicy count tool 48 | func AccessControlPolicyCountHandler() server.ToolHandlerFunc { 49 | return CreateCountToolHandler( 50 | resources.ResourceTypeAccessControlPolicy, 51 | // Define the ListResourceFunc implementation 52 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 53 | 54 | // Use ListAll function to get all resources 55 | resp, err := client.V3().ListAllAccessControlPolicy(ctx, "") 56 | 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | res := map[string]interface{}{ 62 | "resource_type": "AccessControlPolicy", 63 | "count": len(resp.Entities), 64 | "metadata": resp.Metadata, 65 | } 66 | 67 | return res, nil 68 | }, 69 | ) 70 | } 71 | ``` -------------------------------------------------------------------------------- /pkg/tools/networksecurityrule.go: -------------------------------------------------------------------------------- ```go 1 | package tools 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | "github.com/thunderboltsid/mcp-nutanix/pkg/resources" 8 | 9 | "github.com/mark3labs/mcp-go/mcp" 10 | "github.com/mark3labs/mcp-go/server" 11 | ) 12 | 13 | // NetworkSecurityRule defines the NetworkSecurityRule tool 14 | func NetworkSecurityRuleList() mcp.Tool { 15 | return mcp.NewTool("networksecurityrule_list", 16 | mcp.WithDescription("List networksecurityrule resources"), 17 | mcp.WithString("filter", 18 | mcp.Description("Optional text filter (interpreted by LLM)"), 19 | ), 20 | ) 21 | } 22 | 23 | // NetworkSecurityRuleListHandler implements the handler for the NetworkSecurityRule list tool 24 | func NetworkSecurityRuleListHandler() server.ToolHandlerFunc { 25 | return CreateListToolHandler( 26 | resources.ResourceTypeNetworkSecurityRule, 27 | // Define the ListResourceFunc implementation 28 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 29 | 30 | // Use ListAll function to get all resources 31 | return client.V3().ListAllNetworkSecurityRule(ctx, "") 32 | 33 | }, 34 | ) 35 | } 36 | 37 | // NetworkSecurityRuleCount defines the NetworkSecurityRule count tool 38 | func NetworkSecurityRuleCount() mcp.Tool { 39 | return mcp.NewTool("networksecurityrule_count", 40 | mcp.WithDescription("Count networksecurityrule resources"), 41 | mcp.WithString("filter", 42 | mcp.Description("Optional text filter (interpreted by LLM)"), 43 | ), 44 | ) 45 | } 46 | 47 | // NetworkSecurityRuleCountHandler implements the handler for the NetworkSecurityRule count tool 48 | func NetworkSecurityRuleCountHandler() server.ToolHandlerFunc { 49 | return CreateCountToolHandler( 50 | resources.ResourceTypeNetworkSecurityRule, 51 | // Define the ListResourceFunc implementation 52 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 53 | 54 | // Use ListAll function to get all resources 55 | resp, err := client.V3().ListAllNetworkSecurityRule(ctx, "") 56 | 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | res := map[string]interface{}{ 62 | "resource_type": "NetworkSecurityRule", 63 | "count": len(resp.Entities), 64 | "metadata": resp.Metadata, 65 | } 66 | 67 | return res, nil 68 | }, 69 | ) 70 | } 71 | ``` -------------------------------------------------------------------------------- /pkg/tools/recoveryplanjob.go: -------------------------------------------------------------------------------- ```go 1 | package tools 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | "github.com/thunderboltsid/mcp-nutanix/pkg/resources" 8 | 9 | "github.com/mark3labs/mcp-go/mcp" 10 | "github.com/mark3labs/mcp-go/server" 11 | v3 "github.com/nutanix-cloud-native/prism-go-client/v3" 12 | ) 13 | 14 | // RecoveryPlanJob defines the RecoveryPlanJob tool 15 | func RecoveryPlanJobList() mcp.Tool { 16 | return mcp.NewTool("recoveryplanjob_list", 17 | mcp.WithDescription("List recoveryplanjob resources"), 18 | mcp.WithString("filter", 19 | mcp.Description("Optional text filter (interpreted by LLM)"), 20 | ), 21 | ) 22 | } 23 | 24 | // RecoveryPlanJobListHandler implements the handler for the RecoveryPlanJob list tool 25 | func RecoveryPlanJobListHandler() server.ToolHandlerFunc { 26 | return CreateListToolHandler( 27 | resources.ResourceTypeRecoveryPlanJob, 28 | // Define the ListResourceFunc implementation 29 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 30 | 31 | // Create DSMetadata without filter 32 | metadata := &v3.DSMetadata{} 33 | 34 | return client.V3().ListRecoveryPlanJobs(ctx, metadata) 35 | 36 | }, 37 | ) 38 | } 39 | 40 | // RecoveryPlanJobCount defines the RecoveryPlanJob count tool 41 | func RecoveryPlanJobCount() mcp.Tool { 42 | return mcp.NewTool("recoveryplanjob_count", 43 | mcp.WithDescription("Count recoveryplanjob resources"), 44 | mcp.WithString("filter", 45 | mcp.Description("Optional text filter (interpreted by LLM)"), 46 | ), 47 | ) 48 | } 49 | 50 | // RecoveryPlanJobCountHandler implements the handler for the RecoveryPlanJob count tool 51 | func RecoveryPlanJobCountHandler() server.ToolHandlerFunc { 52 | return CreateCountToolHandler( 53 | resources.ResourceTypeRecoveryPlanJob, 54 | // Define the ListResourceFunc implementation 55 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 56 | 57 | // Create DSMetadata without filter 58 | metadata := &v3.DSMetadata{} 59 | 60 | resp, err := client.V3().ListRecoveryPlanJobs(ctx, metadata) 61 | 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | res := map[string]interface{}{ 67 | "resource_type": "RecoveryPlanJob", 68 | "count": len(resp.Entities), 69 | "metadata": resp.Metadata, 70 | } 71 | 72 | return res, nil 73 | }, 74 | ) 75 | } 76 | ``` -------------------------------------------------------------------------------- /pkg/prompts/credentials.go: -------------------------------------------------------------------------------- ```go 1 | package prompts 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 8 | 9 | "github.com/mark3labs/mcp-go/mcp" 10 | "github.com/mark3labs/mcp-go/server" 11 | ) 12 | 13 | func SetCredentials() mcp.Prompt { 14 | return mcp.NewPrompt("credentials", 15 | mcp.WithPromptDescription("Credentials for Prism Central"), 16 | mcp.WithArgument("endpoint", 17 | mcp.RequiredArgument(), 18 | mcp.ArgumentDescription("Prism Central endpoint"), 19 | ), 20 | mcp.WithArgument("username", 21 | mcp.RequiredArgument(), 22 | mcp.ArgumentDescription("Username of the Prism Central user"), 23 | ), 24 | mcp.WithArgument("password", 25 | mcp.RequiredArgument(), 26 | mcp.ArgumentDescription("Password of the Prism Central user"), 27 | ), 28 | mcp.WithArgument("insecure", 29 | mcp.ArgumentDescription("Skip TLS verification (true/false)"), 30 | ), 31 | ) 32 | } 33 | 34 | func SetCredentialsResponse() server.PromptHandlerFunc { 35 | return func(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) { 36 | endpoint := request.Params.Arguments["endpoint"] 37 | username := request.Params.Arguments["username"] 38 | password := request.Params.Arguments["password"] 39 | insecure := request.Params.Arguments["insecure"] 40 | 41 | client.PrismClientProvider.UpdateValue("endpoint", endpoint) 42 | client.PrismClientProvider.UpdateValue("username", username) 43 | client.PrismClientProvider.UpdateValue("password", password) 44 | client.PrismClientProvider.UpdateValue("insecure", insecure) 45 | 46 | client.Init(client.PrismClientProvider) 47 | 48 | // Validate the credentials 49 | pcInfo, err := client.GetPrismClient().V3().GetPrismCentral(ctx) 50 | if err != nil { 51 | return mcp.NewGetPromptResult( 52 | "Failed to connect to Prism Central", 53 | []mcp.PromptMessage{ 54 | mcp.NewPromptMessage( 55 | mcp.RoleAssistant, 56 | mcp.NewTextContent(fmt.Sprintf("Failed to connect to Prism Central: %s. Please check your credentials and try again.", err.Error())), 57 | ), 58 | }, 59 | ), nil 60 | } 61 | 62 | return mcp.NewGetPromptResult( 63 | "Connected to Prism Central", 64 | []mcp.PromptMessage{ 65 | mcp.NewPromptMessage( 66 | mcp.RoleAssistant, 67 | mcp.NewTextContent(fmt.Sprintf("Successfully connected to Prism Central %s at %s. You can now use the tools to interact with your Nutanix environment.", pcInfo.Resources.ClusterUUID, endpoint)), 68 | ), 69 | }, 70 | ), nil 71 | } 72 | } 73 | ``` -------------------------------------------------------------------------------- /pkg/tools/common.go: -------------------------------------------------------------------------------- ```go 1 | package tools 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 7 | "github.com/thunderboltsid/mcp-nutanix/internal/json" 8 | "github.com/thunderboltsid/mcp-nutanix/pkg/resources" 9 | 10 | "github.com/mark3labs/mcp-go/mcp" 11 | "github.com/mark3labs/mcp-go/server" 12 | ) 13 | 14 | // ListResourceFunc defines a function that handles listing a resource type 15 | type ListResourceFunc func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) 16 | 17 | // CreateListToolHandler creates a generic tool handler for listing resources 18 | func CreateListToolHandler( 19 | resourceType resources.ResourceType, 20 | listFunc ListResourceFunc, 21 | ) server.ToolHandlerFunc { 22 | return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 23 | // Get the Prism client 24 | prismClient := client.GetPrismClient() 25 | if prismClient == nil { 26 | return nil, fmt.Errorf("prism client not initialized, please set credentials first") 27 | } 28 | 29 | // Get filter if provided (for LLM reference only) 30 | //filter, _ := request.Params.Arguments["filter"].(string) 31 | 32 | // List all resources 33 | resp, err := listFunc(ctx, prismClient, "") 34 | if err != nil { 35 | return nil, fmt.Errorf("failed to list %s: %w", resourceType, err) 36 | } 37 | 38 | // Convert to JSON 39 | cjson := json.CustomJSONEncoder(resp) 40 | jsonBytes, err := cjson.MarshalJSON() 41 | if err != nil { 42 | return nil, fmt.Errorf("failed to marshal %s: %w", resourceType, err) 43 | } 44 | 45 | return mcp.NewToolResultText(string(jsonBytes)), nil 46 | } 47 | } 48 | 49 | // CreateCountToolHandler creates a generic tool handler for counting resources 50 | func CreateCountToolHandler( 51 | resourceType resources.ResourceType, 52 | countFunc ListResourceFunc, 53 | ) server.ToolHandlerFunc { 54 | return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 55 | // Get the Prism client 56 | prismClient := client.GetPrismClient() 57 | if prismClient == nil { 58 | return nil, fmt.Errorf("prism client not initialized, please set credentials first") 59 | } 60 | 61 | // Get filter if provided (for LLM reference only) 62 | //filter, _ := request.Params.Arguments["filter"].(string) 63 | 64 | // List all resources 65 | resp, err := countFunc(ctx, prismClient, "") 66 | if err != nil { 67 | return nil, fmt.Errorf("failed to list %s: %w", resourceType, err) 68 | } 69 | 70 | // Convert to JSON 71 | cjson := json.RegularJSONEncoder(resp) 72 | jsonBytes, err := cjson.MarshalJSON() 73 | if err != nil { 74 | return nil, fmt.Errorf("failed to marshal %s count: %w", resourceType, err) 75 | } 76 | 77 | return mcp.NewToolResultText(string(jsonBytes)), nil 78 | } 79 | } 80 | ``` -------------------------------------------------------------------------------- /internal/json/marshal.go: -------------------------------------------------------------------------------- ```go 1 | package json 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/itchyny/gojq" 9 | ) 10 | 11 | // DefaultStripPaths is a list of default paths to strip from the JSON output. 12 | // These paths are used to remove unnecessary or sensitive information from the JSON response. 13 | // guest_customization on VMs is massive and could contain sensitive information. 14 | // spec and status are duplicative info and hence status can be removed. 15 | var DefaultStripPaths = []string{ 16 | "api_version", 17 | "spec.resources.guest_customization", 18 | "entities[].spec.resources.guest_customization", 19 | "entities[].status.resources.guest_customization", 20 | } 21 | 22 | func stripProperties(data []byte, paths []string) ([]byte, error) { 23 | var input interface{} 24 | if err := json.Unmarshal(data, &input); err != nil { 25 | return nil, err 26 | } 27 | 28 | // Start with identity query 29 | queryStr := "." 30 | 31 | for _, path := range paths { 32 | if strings.Contains(path, "[]") { 33 | // Handle array paths (entities[].status) 34 | parts := strings.Split(path, "[]") 35 | arrayPath := parts[0] 36 | fieldPath := parts[1] 37 | 38 | if len(fieldPath) > 0 && fieldPath[0] == '.' { 39 | fieldPath = fieldPath[1:] // Remove leading dot 40 | } 41 | 42 | // Correct jq syntax for modifying each element in an array 43 | queryStr += fmt.Sprintf(" | .%s |= map(del(.%s))", arrayPath, fieldPath) 44 | } else { 45 | // Simple path (api_version) 46 | queryStr += fmt.Sprintf(" | del(.%s)", path) 47 | } 48 | } 49 | 50 | // For debugging 51 | // fmt.Printf("JQ Query: %s\n", queryStr) 52 | 53 | query, err := gojq.Parse(queryStr) 54 | if err != nil { 55 | return nil, fmt.Errorf("jq parse error: %v for query: %s", err, queryStr) 56 | } 57 | 58 | code, err := gojq.Compile(query) 59 | if err != nil { 60 | return nil, fmt.Errorf("jq compile error: %v", err) 61 | } 62 | 63 | iter := code.Run(input) 64 | result, ok := iter.Next() 65 | if !ok { 66 | return nil, fmt.Errorf("jq query returned no results") 67 | } 68 | 69 | if err, ok := result.(error); ok { 70 | return nil, fmt.Errorf("jq execution error: %v", err) 71 | } 72 | 73 | return json.Marshal(result) 74 | } 75 | 76 | type CustomJSON struct { 77 | Value interface{} 78 | StripPaths []string 79 | } 80 | 81 | type RegularJSON struct { 82 | Value interface{} 83 | } 84 | 85 | func CustomJSONEncoder(value any) *CustomJSON { 86 | return &CustomJSON{ 87 | Value: value, 88 | StripPaths: DefaultStripPaths, 89 | } 90 | } 91 | 92 | func RegularJSONEncoder(value any) *RegularJSON { 93 | return &RegularJSON{ 94 | Value: value, 95 | } 96 | } 97 | 98 | func (r *RegularJSON) MarshalJSON() ([]byte, error) { 99 | data, err := json.Marshal(r.Value) 100 | if err != nil { 101 | return nil, err 102 | } 103 | 104 | return data, nil 105 | } 106 | 107 | func (d *CustomJSON) MarshalJSON() ([]byte, error) { 108 | data, err := json.Marshal(d.Value) 109 | if err != nil { 110 | return nil, err 111 | } 112 | 113 | return stripProperties(data, d.StripPaths) 114 | } 115 | ``` -------------------------------------------------------------------------------- /pkg/resources/common.go: -------------------------------------------------------------------------------- ```go 1 | package resources 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 9 | "github.com/thunderboltsid/mcp-nutanix/internal/json" 10 | 11 | "github.com/mark3labs/mcp-go/mcp" 12 | "github.com/mark3labs/mcp-go/server" 13 | ) 14 | 15 | // ResourceType enum for different resource types 16 | type ResourceType string 17 | 18 | const ( 19 | ResourceTypeVM ResourceType = "vm" 20 | ResourceTypeSubnet ResourceType = "subnet" 21 | ResourceTypeImage ResourceType = "image" 22 | ResourceTypeCluster ResourceType = "cluster" 23 | ResourceTypeHost ResourceType = "host" 24 | ResourceTypeProject ResourceType = "project" 25 | ResourceTypeVolumeGroup ResourceType = "volumegroup" 26 | ResourceTypeNetworkSecurityRule ResourceType = "networksecurityrule" 27 | ResourceTypeServiceGroup ResourceType = "servicegroup" 28 | ResourceTypeAddressGroup ResourceType = "addressgroup" 29 | ResourceTypeAccessControlPolicy ResourceType = "accesscontrolpolicy" 30 | ResourceTypeRole ResourceType = "role" 31 | ResourceTypeUser ResourceType = "user" 32 | ResourceTypeUserGroup ResourceType = "usergroup" 33 | ResourceTypePermission ResourceType = "permission" 34 | ResourceTypeProtectionRule ResourceType = "protectionrule" 35 | ResourceTypeRecoveryPlan ResourceType = "recoveryplan" 36 | ResourceTypeRecoveryPlanJob ResourceType = "recoveryplanjob" 37 | ResourceTypeCategory ResourceType = "category" 38 | ResourceTypeCategoryValue ResourceType = "categoryvalue" 39 | ResourceTypeAvailabilityZone ResourceType = "availabilityzone" 40 | ) 41 | 42 | // ResourceHandlerFunc defines a function that handles a specific resource get operation 43 | type ResourceHandlerFunc func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) 44 | 45 | // ResourceURIPrefix returns the URI prefix for a resource type 46 | func ResourceURIPrefix(resourceType ResourceType) string { 47 | return fmt.Sprintf("%s://", resourceType) 48 | } 49 | 50 | // NutanixURI returns a URI for a resource type and UUID 51 | func NutanixURI(resourceType ResourceType, uuid string) string { 52 | return fmt.Sprintf("%s://%s", resourceType, uuid) 53 | } 54 | 55 | // ExtractIDFromURI extracts the UUID from a URI 56 | // uri is expected to be in the format of resourceType://uuid 57 | func ExtractIDFromURI(uri string) string { 58 | parts := strings.Split(uri, "://") 59 | if len(parts) != 2 { 60 | return "" 61 | } 62 | return parts[1] 63 | } 64 | 65 | // ExtractTypeFromURI extracts the resource type from a URI 66 | // uri is expected to be in the format of resourceType://uuid 67 | func ExtractTypeFromURI(uri string) ResourceType { 68 | parts := strings.Split(uri, "://") 69 | if len(parts) != 2 { 70 | return "" 71 | } 72 | return ResourceType(parts[0]) 73 | } 74 | 75 | // CreateResourceHandler creates a generic resource handler for any Nutanix resource 76 | func CreateResourceHandler(resourceType ResourceType, handlerFunc ResourceHandlerFunc) server.ResourceTemplateHandlerFunc { 77 | return func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) { 78 | uuid := ExtractIDFromURI(request.Params.URI) 79 | if uuid == "" { 80 | return nil, fmt.Errorf("URI must contain a UUID") 81 | } 82 | 83 | // Get the Prism client 84 | prismClient := client.GetPrismClient() 85 | if prismClient == nil { 86 | return nil, fmt.Errorf("prism client not initialized, please set credentials first") 87 | } 88 | 89 | // Call the specific resource handler 90 | resource, err := handlerFunc(ctx, prismClient, uuid) 91 | if err != nil { 92 | return nil, fmt.Errorf("failed to get %s: %w", resourceType, err) 93 | } 94 | 95 | // Convert to JSON 96 | cjson := json.CustomJSONEncoder(resource) 97 | jsonBytes, err := cjson.MarshalJSON() 98 | if err != nil { 99 | return nil, fmt.Errorf("failed to marshal %s details: %w", resourceType, err) 100 | } 101 | 102 | return []mcp.ResourceContents{ 103 | &mcp.TextResourceContents{ 104 | URI: request.Params.URI, 105 | MIMEType: "application/json", 106 | Text: string(jsonBytes), 107 | }, 108 | }, nil 109 | } 110 | } 111 | ``` -------------------------------------------------------------------------------- /internal/codegen/templates/tools.go: -------------------------------------------------------------------------------- ```go 1 | package templates 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | "text/template" 8 | ) 9 | 10 | // Templates for tool implementations 11 | const toolTemplate = `package tools 12 | 13 | import ( 14 | "context" 15 | "fmt" 16 | 17 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 18 | "github.com/thunderboltsid/mcp-nutanix/pkg/resources" 19 | 20 | "github.com/mark3labs/mcp-go/mcp" 21 | "github.com/mark3labs/mcp-go/server" 22 | "github.com/nutanix-cloud-native/prism-go-client/v3" 23 | ) 24 | 25 | // {{.Name}} defines the {{.Name}} tool 26 | func {{.Name}}List() mcp.Tool { 27 | return mcp.NewTool("{{.ResourceType}}_list", 28 | mcp.WithDescription("List {{.ResourceType}} resources"), 29 | mcp.WithString("filter", 30 | mcp.Description("Optional text filter (interpreted by LLM)"), 31 | ), 32 | ) 33 | } 34 | 35 | // {{.Name}}ListHandler implements the handler for the {{.Name}} list tool 36 | func {{.Name}}ListHandler() server.ToolHandlerFunc { 37 | return CreateListToolHandler( 38 | resources.ResourceType{{.Name}}, 39 | // Define the ListResourceFunc implementation 40 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 41 | {{if eq .Name "Host"}} 42 | // Special case for Host which doesn't take a filter 43 | return client.V3().{{.ClientListAllFunc}}(ctx) 44 | {{else if eq .Name "Subnet"}} 45 | // Special case for Subnet which has an extra parameter 46 | return client.V3().{{.ClientListAllFunc}}(ctx, "", nil) 47 | {{else if eq .Name "Category"}} 48 | // Special case for Category which takes CategoryListMetadata 49 | metadata := &v3.CategoryListMetadata{} 50 | return client.V3().{{.ClientListFunc}}(ctx, metadata) 51 | {{else if .HasListAllFunc}} 52 | // Use ListAll function to get all resources 53 | return client.V3().{{.ClientListAllFunc}}(ctx, "") 54 | {{else}} 55 | // Create DSMetadata without filter 56 | metadata := &v3.DSMetadata{} 57 | 58 | return client.V3().{{.ClientListFunc}}(ctx, metadata) 59 | {{end}} 60 | }, 61 | ) 62 | } 63 | 64 | // {{.Name}}Count defines the {{.Name}} count tool 65 | func {{.Name}}Count() mcp.Tool { 66 | return mcp.NewTool("{{.ResourceType}}_count", 67 | mcp.WithDescription("Count {{.ResourceType}} resources"), 68 | mcp.WithString("filter", 69 | mcp.Description("Optional text filter (interpreted by LLM)"), 70 | ), 71 | ) 72 | } 73 | 74 | // {{.Name}}CountHandler implements the handler for the {{.Name}} count tool 75 | func {{.Name}}CountHandler() server.ToolHandlerFunc { 76 | return CreateCountToolHandler( 77 | resources.ResourceType{{.Name}}, 78 | // Define the ListResourceFunc implementation 79 | func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { 80 | {{if eq .Name "Host"}} 81 | // Special case for Host which doesn't take a filter 82 | resp, err := client.V3().{{.ClientListAllFunc}}(ctx) 83 | {{else if eq .Name "Subnet"}} 84 | // Special case for Subnet which has an extra parameter 85 | resp, err := client.V3().{{.ClientListAllFunc}}(ctx, "", nil) 86 | {{else if eq .Name "Category"}} 87 | // Special case for Category which takes CategoryListMetadata 88 | metadata := &v3.CategoryListMetadata{} 89 | resp, err := client.V3().{{.ClientListFunc}}(ctx, metadata) 90 | {{else if .HasListAllFunc}} 91 | // Use ListAll function to get all resources 92 | resp, err := client.V3().{{.ClientListAllFunc}}(ctx, "") 93 | {{else}} 94 | // Create DSMetadata without filter 95 | metadata := &v3.DSMetadata{} 96 | 97 | resp, err := client.V3().{{.ClientListFunc}}(ctx, metadata) 98 | {{end}} 99 | if err != nil { 100 | return nil, err 101 | } 102 | 103 | res := map[string]interface{}{ 104 | "resource_type": "{{.Name}}", 105 | "count": len(resp.Entities), 106 | "metadata": resp.Metadata, 107 | } 108 | 109 | return res, nil 110 | }, 111 | ) 112 | } 113 | ` 114 | 115 | // GenerateToolFiles generates tool files for all Nutanix resources that support listing 116 | func GenerateToolFiles(baseDir string) error { 117 | resources := GetResourceDefinitions() 118 | 119 | // Create the tools directory if it doesn't exist 120 | toolsDir := fmt.Sprintf("%s/pkg/tools", baseDir) 121 | err := os.MkdirAll(toolsDir, 0755) 122 | if err != nil { 123 | return fmt.Errorf("error creating tools directory: %w", err) 124 | } 125 | 126 | // Parse the tool template 127 | toolTmpl, err := template.New("tool").Parse(toolTemplate) 128 | if err != nil { 129 | return fmt.Errorf("error parsing tool template: %w", err) 130 | } 131 | 132 | // Generate tool files 133 | for _, res := range resources { 134 | // Skip resources that don't support listing 135 | if !res.HasListFunc && !res.HasListAllFunc { 136 | fmt.Printf("Skipping tool generation for %s: no list capability\n", res.Name) 137 | continue 138 | } 139 | 140 | // Create tool file 141 | toolFilePath := fmt.Sprintf("%s/%s.go", toolsDir, strings.ToLower(res.Name)) 142 | toolFile, err := os.Create(toolFilePath) 143 | if err != nil { 144 | fmt.Printf("Error creating tool file for %s: %v\n", res.Name, err) 145 | continue 146 | } 147 | defer toolFile.Close() 148 | 149 | // Execute the template 150 | err = toolTmpl.Execute(toolFile, res) 151 | if err != nil { 152 | fmt.Printf("Error executing tool template for %s: %v\n", res.Name, err) 153 | } 154 | } 155 | 156 | return nil 157 | } 158 | ``` -------------------------------------------------------------------------------- /internal/codegen/templates/resources.go: -------------------------------------------------------------------------------- ```go 1 | package templates 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | "text/template" 8 | ) 9 | 10 | // Resource defines the structure for a Nutanix resource 11 | type Resource struct { 12 | Name string 13 | ResourceType string 14 | Description string 15 | ClientGetFunc string 16 | ClientListFunc string // Regular List function with DSMetadata parameter 17 | ClientListAllFunc string // ListAll function with filter string parameter 18 | HasListFunc bool // Whether the service has a ListX function 19 | HasListAllFunc bool // Whether the service has a ListAllX function 20 | } 21 | 22 | const resourceTemplate = `package resources 23 | 24 | import ( 25 | "context" 26 | 27 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 28 | 29 | "github.com/mark3labs/mcp-go/mcp" 30 | "github.com/mark3labs/mcp-go/server" 31 | ) 32 | 33 | // {{.Name}} defines the {{.Name}} resource template 34 | func {{.Name}}() mcp.ResourceTemplate { 35 | return mcp.NewResourceTemplate( 36 | string(ResourceURIPrefix(ResourceType{{.Name}})) + "{uuid}", 37 | string(ResourceType{{.Name}}), 38 | mcp.WithTemplateDescription("{{.Description}}"), 39 | mcp.WithTemplateMIMEType("application/json"), 40 | ) 41 | } 42 | 43 | // {{.Name}}Handler implements the handler for the {{.Name}} resource 44 | func {{.Name}}Handler() server.ResourceTemplateHandlerFunc { 45 | return CreateResourceHandler(ResourceType{{.Name}}, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { 46 | // Get the {{.Name}} 47 | return client.V3().{{.ClientGetFunc}}(ctx, uuid) 48 | }) 49 | } 50 | ` 51 | 52 | // GetResourceDefinitions returns all Nutanix resource definitions 53 | func GetResourceDefinitions() []Resource { 54 | return []Resource{ 55 | { 56 | Name: "VM", 57 | ResourceType: "vm", 58 | Description: "Virtual Machine resource", 59 | ClientGetFunc: "GetVM", 60 | ClientListFunc: "ListVM", 61 | ClientListAllFunc: "ListAllVM", 62 | HasListFunc: true, 63 | HasListAllFunc: true, 64 | }, 65 | { 66 | Name: "Cluster", 67 | ResourceType: "cluster", 68 | Description: "Cluster resource", 69 | ClientGetFunc: "GetCluster", 70 | ClientListFunc: "ListCluster", 71 | ClientListAllFunc: "ListAllCluster", 72 | HasListFunc: true, 73 | HasListAllFunc: true, 74 | }, 75 | { 76 | Name: "Image", 77 | ResourceType: "image", 78 | Description: "Image resource", 79 | ClientGetFunc: "GetImage", 80 | ClientListFunc: "ListImage", 81 | ClientListAllFunc: "ListAllImage", 82 | HasListFunc: true, 83 | HasListAllFunc: true, 84 | }, 85 | { 86 | Name: "Subnet", 87 | ResourceType: "subnet", 88 | Description: "Subnet resource", 89 | ClientGetFunc: "GetSubnet", 90 | ClientListFunc: "ListSubnet", 91 | ClientListAllFunc: "ListAllSubnet", 92 | HasListFunc: true, 93 | HasListAllFunc: true, 94 | }, 95 | { 96 | Name: "Host", 97 | ResourceType: "host", 98 | Description: "Host resource", 99 | ClientGetFunc: "GetHost", 100 | ClientListFunc: "ListHost", 101 | ClientListAllFunc: "ListAllHost", 102 | HasListFunc: true, 103 | HasListAllFunc: true, 104 | }, 105 | { 106 | Name: "Project", 107 | ResourceType: "project", 108 | Description: "Project resource", 109 | ClientGetFunc: "GetProject", 110 | ClientListFunc: "ListProject", 111 | ClientListAllFunc: "ListAllProject", 112 | HasListFunc: true, 113 | HasListAllFunc: true, 114 | }, 115 | { 116 | Name: "VolumeGroup", 117 | ResourceType: "volumegroup", 118 | Description: "Volume Group resource", 119 | ClientGetFunc: "GetVolumeGroup", 120 | ClientListFunc: "ListVolumeGroup", 121 | ClientListAllFunc: "", 122 | HasListFunc: true, 123 | HasListAllFunc: false, 124 | }, 125 | { 126 | Name: "NetworkSecurityRule", 127 | ResourceType: "networksecurityrule", 128 | Description: "Network Security Rule resource", 129 | ClientGetFunc: "GetNetworkSecurityRule", 130 | ClientListFunc: "ListNetworkSecurityRule", 131 | ClientListAllFunc: "ListAllNetworkSecurityRule", 132 | HasListFunc: true, 133 | HasListAllFunc: true, 134 | }, 135 | { 136 | Name: "Category", 137 | ResourceType: "category", 138 | Description: "Category resource", 139 | ClientGetFunc: "GetCategoryKey", 140 | ClientListFunc: "ListCategories", 141 | ClientListAllFunc: "", 142 | HasListFunc: true, 143 | HasListAllFunc: false, 144 | }, 145 | { 146 | Name: "AccessControlPolicy", 147 | ResourceType: "accesscontrolpolicy", 148 | Description: "Access Control Policy resource", 149 | ClientGetFunc: "GetAccessControlPolicy", 150 | ClientListFunc: "ListAccessControlPolicy", 151 | ClientListAllFunc: "ListAllAccessControlPolicy", 152 | HasListFunc: true, 153 | HasListAllFunc: true, 154 | }, 155 | { 156 | Name: "Role", 157 | ResourceType: "role", 158 | Description: "Role resource", 159 | ClientGetFunc: "GetRole", 160 | ClientListFunc: "ListRole", 161 | ClientListAllFunc: "ListAllRole", 162 | HasListFunc: true, 163 | HasListAllFunc: true, 164 | }, 165 | { 166 | Name: "User", 167 | ResourceType: "user", 168 | Description: "User resource", 169 | ClientGetFunc: "GetUser", 170 | ClientListFunc: "ListUser", 171 | ClientListAllFunc: "ListAllUser", 172 | HasListFunc: true, 173 | HasListAllFunc: true, 174 | }, 175 | { 176 | Name: "UserGroup", 177 | ResourceType: "usergroup", 178 | Description: "User Group resource", 179 | ClientGetFunc: "GetUserGroup", 180 | ClientListFunc: "ListUserGroup", 181 | ClientListAllFunc: "ListAllUserGroup", 182 | HasListFunc: true, 183 | HasListAllFunc: true, 184 | }, 185 | { 186 | Name: "Permission", 187 | ResourceType: "permission", 188 | Description: "Permission resource", 189 | ClientGetFunc: "GetPermission", 190 | ClientListFunc: "ListPermission", 191 | ClientListAllFunc: "ListAllPermission", 192 | HasListFunc: true, 193 | HasListAllFunc: true, 194 | }, 195 | { 196 | Name: "ProtectionRule", 197 | ResourceType: "protectionrule", 198 | Description: "Protection Rule resource", 199 | ClientGetFunc: "GetProtectionRule", 200 | ClientListFunc: "ListProtectionRules", 201 | ClientListAllFunc: "ListAllProtectionRules", 202 | HasListFunc: true, 203 | HasListAllFunc: true, 204 | }, 205 | { 206 | Name: "RecoveryPlan", 207 | ResourceType: "recoveryplan", 208 | Description: "Recovery Plan resource", 209 | ClientGetFunc: "GetRecoveryPlan", 210 | ClientListFunc: "ListRecoveryPlans", 211 | ClientListAllFunc: "ListAllRecoveryPlans", 212 | HasListFunc: true, 213 | HasListAllFunc: true, 214 | }, 215 | { 216 | Name: "ServiceGroup", 217 | ResourceType: "servicegroup", 218 | Description: "Service Group resource", 219 | ClientGetFunc: "GetServiceGroup", 220 | ClientListFunc: "", 221 | ClientListAllFunc: "ListAllServiceGroups", 222 | HasListFunc: false, 223 | HasListAllFunc: true, 224 | }, 225 | { 226 | Name: "AddressGroup", 227 | ResourceType: "addressgroup", 228 | Description: "Address Group resource", 229 | ClientGetFunc: "GetAddressGroup", 230 | ClientListFunc: "ListAddressGroups", 231 | ClientListAllFunc: "ListAllAddressGroups", 232 | HasListFunc: true, 233 | HasListAllFunc: true, 234 | }, 235 | { 236 | Name: "RecoveryPlanJob", 237 | ResourceType: "recoveryplanjob", 238 | Description: "Recovery Plan Job resource", 239 | ClientGetFunc: "GetRecoveryPlanJob", 240 | ClientListFunc: "ListRecoveryPlanJobs", 241 | ClientListAllFunc: "", 242 | HasListFunc: true, 243 | HasListAllFunc: false, 244 | }, 245 | { 246 | Name: "AvailabilityZone", 247 | ResourceType: "availabilityzone", 248 | Description: "Availability Zone resource", 249 | ClientGetFunc: "GetAvailabilityZone", 250 | ClientListFunc: "", 251 | ClientListAllFunc: "", 252 | HasListFunc: false, 253 | HasListAllFunc: false, 254 | }, 255 | } 256 | } 257 | 258 | // GenerateResourceFiles generates resource files for all Nutanix resources 259 | func GenerateResourceFiles(baseDir string) error { 260 | resources := GetResourceDefinitions() 261 | 262 | // Create the resources directory if it doesn't exist 263 | resourcesDir := fmt.Sprintf("%s/pkg/resources", baseDir) 264 | err := os.MkdirAll(resourcesDir, 0755) 265 | if err != nil { 266 | return fmt.Errorf("error creating resources directory: %w", err) 267 | } 268 | 269 | // Parse the resource template 270 | tmpl, err := template.New("resource").Parse(resourceTemplate) 271 | if err != nil { 272 | return fmt.Errorf("error parsing resource template: %w", err) 273 | } 274 | 275 | // Generate resource files 276 | for _, res := range resources { 277 | // Create resource file 278 | resourceFilePath := fmt.Sprintf("%s/%s.go", resourcesDir, strings.ToLower(res.Name)) 279 | resourceFile, err := os.Create(resourceFilePath) 280 | if err != nil { 281 | fmt.Printf("Error creating resource file for %s: %v\n", res.Name, err) 282 | continue 283 | } 284 | defer resourceFile.Close() 285 | 286 | // Execute the template 287 | err = tmpl.Execute(resourceFile, res) 288 | if err != nil { 289 | fmt.Printf("Error executing resource template for %s: %v\n", res.Name, err) 290 | } 291 | } 292 | 293 | return nil 294 | } 295 | ``` -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- ```go 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/thunderboltsid/mcp-nutanix/internal/client" 8 | "github.com/thunderboltsid/mcp-nutanix/pkg/prompts" 9 | "github.com/thunderboltsid/mcp-nutanix/pkg/resources" 10 | "github.com/thunderboltsid/mcp-nutanix/pkg/tools" 11 | 12 | "github.com/mark3labs/mcp-go/mcp" 13 | "github.com/mark3labs/mcp-go/server" 14 | ) 15 | 16 | // ToolRegistration holds a tool function and its handler 17 | type ToolRegistration struct { 18 | Func func() mcp.Tool 19 | Handler server.ToolHandlerFunc 20 | } 21 | 22 | // ResourceRegistration represents a resource and its associated tools 23 | type ResourceRegistration struct { 24 | Tools []ToolRegistration 25 | ResourceFunc func() mcp.ResourceTemplate 26 | ResourceHandler server.ResourceTemplateHandlerFunc 27 | } 28 | 29 | // initializeFromEnvIfAvailable initializes the Prism client only if environment variables are available 30 | func initializeFromEnvIfAvailable() { 31 | endpoint := os.Getenv("NUTANIX_ENDPOINT") 32 | username := os.Getenv("NUTANIX_USERNAME") 33 | password := os.Getenv("NUTANIX_PASSWORD") 34 | 35 | // Only initialize if all required environment variables are set 36 | // This allows prompt-based initialization to work when env vars are not present 37 | if endpoint != "" && username != "" && password != "" { 38 | client.Init(client.PrismClientProvider) 39 | fmt.Printf("Initialized Prism client from environment variables for endpoint: %s\n", endpoint) 40 | } 41 | } 42 | 43 | func main() { 44 | // Initialize the Prism client only if environment variables are available 45 | initializeFromEnvIfAvailable() 46 | 47 | // Define server hooks for logging and debugging 48 | hooks := &server.Hooks{} 49 | hooks.AddOnError(func(id any, method mcp.MCPMethod, message any, err error) { 50 | fmt.Printf("onError: %s, %v, %v, %v\n", method, id, message, err) 51 | }) 52 | 53 | // Log level based on environment variable 54 | debugMode := os.Getenv("DEBUG") != "" 55 | if debugMode { 56 | hooks.AddBeforeAny(func(id any, method mcp.MCPMethod, message any) { 57 | fmt.Printf("beforeAny: %s, %v, %v\n", method, id, message) 58 | }) 59 | hooks.AddOnSuccess(func(id any, method mcp.MCPMethod, message any, result any) { 60 | fmt.Printf("onSuccess: %s, %v, %v, %v\n", method, id, message, result) 61 | }) 62 | hooks.AddBeforeInitialize(func(id any, message *mcp.InitializeRequest) { 63 | fmt.Printf("beforeInitialize: %v, %v\n", id, message) 64 | }) 65 | hooks.AddAfterInitialize(func(id any, message *mcp.InitializeRequest, result *mcp.InitializeResult) { 66 | fmt.Printf("afterInitialize: %v, %v, %v\n", id, message, result) 67 | }) 68 | hooks.AddAfterCallTool(func(id any, message *mcp.CallToolRequest, result *mcp.CallToolResult) { 69 | fmt.Printf("afterCallTool: %v, %v, %v\n", id, message, result) 70 | }) 71 | hooks.AddBeforeCallTool(func(id any, message *mcp.CallToolRequest) { 72 | fmt.Printf("beforeCallTool: %v, %v\n", id, message) 73 | }) 74 | } 75 | 76 | // Create a new MCP server 77 | s := server.NewMCPServer( 78 | "Prism Central", 79 | "0.0.1", 80 | server.WithResourceCapabilities(true, true), 81 | server.WithPromptCapabilities(true), 82 | server.WithLogging(), 83 | server.WithHooks(hooks), 84 | ) 85 | 86 | // Add the prompts 87 | s.AddPrompt(prompts.SetCredentials(), prompts.SetCredentialsResponse()) 88 | 89 | // Add standalone tools 90 | s.AddTool(tools.ApiNamespacesList(), tools.ApiNamespacesListHandler()) 91 | 92 | // Define all resources and tools 93 | resourceRegistrations := map[string]ResourceRegistration{ 94 | "vm": { 95 | Tools: []ToolRegistration{ 96 | { 97 | Func: tools.VMList, 98 | Handler: tools.VMListHandler(), 99 | }, 100 | { 101 | Func: tools.VMCount, 102 | Handler: tools.VMCountHandler(), 103 | }, 104 | }, 105 | ResourceFunc: resources.VM, 106 | ResourceHandler: resources.VMHandler(), 107 | }, 108 | "cluster": { 109 | Tools: []ToolRegistration{ 110 | { 111 | Func: tools.ClusterList, 112 | Handler: tools.ClusterListHandler(), 113 | }, 114 | { 115 | Func: tools.ClusterCount, 116 | Handler: tools.ClusterCountHandler(), 117 | }, 118 | }, 119 | ResourceFunc: resources.Cluster, 120 | ResourceHandler: resources.ClusterHandler(), 121 | }, 122 | "host": { 123 | Tools: []ToolRegistration{ 124 | { 125 | Func: tools.HostList, 126 | Handler: tools.HostListHandler(), 127 | }, 128 | { 129 | Func: tools.HostCount, 130 | Handler: tools.HostCountHandler(), 131 | }, 132 | }, 133 | ResourceFunc: resources.Host, 134 | ResourceHandler: resources.HostHandler(), 135 | }, 136 | "image": { 137 | Tools: []ToolRegistration{ 138 | { 139 | Func: tools.ImageList, 140 | Handler: tools.ImageListHandler(), 141 | }, 142 | { 143 | Func: tools.ImageCount, 144 | Handler: tools.ImageCountHandler(), 145 | }, 146 | }, 147 | ResourceFunc: resources.Image, 148 | ResourceHandler: resources.ImageHandler(), 149 | }, 150 | "subnet": { 151 | Tools: []ToolRegistration{ 152 | { 153 | Func: tools.SubnetList, 154 | Handler: tools.SubnetListHandler(), 155 | }, 156 | { 157 | Func: tools.SubnetCount, 158 | Handler: tools.SubnetCountHandler(), 159 | }, 160 | }, 161 | ResourceFunc: resources.Subnet, 162 | ResourceHandler: resources.SubnetHandler(), 163 | }, 164 | "project": { 165 | Tools: []ToolRegistration{ 166 | { 167 | Func: tools.ProjectList, 168 | Handler: tools.ProjectListHandler(), 169 | }, 170 | { 171 | Func: tools.ProjectCount, 172 | Handler: tools.ProjectCountHandler(), 173 | }, 174 | }, 175 | ResourceFunc: resources.Project, 176 | ResourceHandler: resources.ProjectHandler(), 177 | }, 178 | "volumegroup": { 179 | Tools: []ToolRegistration{ 180 | { 181 | Func: tools.VolumeGroupList, 182 | Handler: tools.VolumeGroupListHandler(), 183 | }, 184 | { 185 | Func: tools.VolumeGroupCount, 186 | Handler: tools.VolumeGroupCountHandler(), 187 | }, 188 | }, 189 | ResourceFunc: resources.VolumeGroup, 190 | ResourceHandler: resources.VolumeGroupHandler(), 191 | }, 192 | "networksecurityrule": { 193 | Tools: []ToolRegistration{ 194 | { 195 | Func: tools.NetworkSecurityRuleList, 196 | Handler: tools.NetworkSecurityRuleListHandler(), 197 | }, 198 | { 199 | Func: tools.NetworkSecurityRuleCount, 200 | Handler: tools.NetworkSecurityRuleCountHandler(), 201 | }, 202 | }, 203 | ResourceFunc: resources.NetworkSecurityRule, 204 | ResourceHandler: resources.NetworkSecurityRuleHandler(), 205 | }, 206 | "category": { 207 | Tools: []ToolRegistration{ 208 | { 209 | Func: tools.CategoryList, 210 | Handler: tools.CategoryListHandler(), 211 | }, 212 | { 213 | Func: tools.CategoryCount, 214 | Handler: tools.CategoryCountHandler(), 215 | }, 216 | }, 217 | ResourceFunc: resources.Category, 218 | ResourceHandler: resources.CategoryHandler(), 219 | }, 220 | "accesscontrolpolicy": { 221 | Tools: []ToolRegistration{ 222 | { 223 | Func: tools.AccessControlPolicyList, 224 | Handler: tools.AccessControlPolicyListHandler(), 225 | }, 226 | { 227 | Func: tools.AccessControlPolicyCount, 228 | Handler: tools.AccessControlPolicyCountHandler(), 229 | }, 230 | }, 231 | ResourceFunc: resources.AccessControlPolicy, 232 | ResourceHandler: resources.AccessControlPolicyHandler(), 233 | }, 234 | "role": { 235 | Tools: []ToolRegistration{ 236 | { 237 | Func: tools.RoleList, 238 | Handler: tools.RoleListHandler(), 239 | }, 240 | { 241 | Func: tools.RoleCount, 242 | Handler: tools.RoleCountHandler(), 243 | }, 244 | }, 245 | ResourceFunc: resources.Role, 246 | ResourceHandler: resources.RoleHandler(), 247 | }, 248 | "user": { 249 | Tools: []ToolRegistration{ 250 | { 251 | Func: tools.UserList, 252 | Handler: tools.UserListHandler(), 253 | }, 254 | { 255 | Func: tools.UserCount, 256 | Handler: tools.UserCountHandler(), 257 | }, 258 | }, 259 | ResourceFunc: resources.User, 260 | ResourceHandler: resources.UserHandler(), 261 | }, 262 | "usergroup": { 263 | Tools: []ToolRegistration{ 264 | { 265 | Func: tools.UserGroupList, 266 | Handler: tools.UserGroupListHandler(), 267 | }, 268 | { 269 | Func: tools.UserGroupCount, 270 | Handler: tools.UserGroupCountHandler(), 271 | }, 272 | }, 273 | ResourceFunc: resources.UserGroup, 274 | ResourceHandler: resources.UserGroupHandler(), 275 | }, 276 | "permission": { 277 | Tools: []ToolRegistration{ 278 | { 279 | Func: tools.PermissionList, 280 | Handler: tools.PermissionListHandler(), 281 | }, 282 | { 283 | Func: tools.PermissionCount, 284 | Handler: tools.PermissionCountHandler(), 285 | }, 286 | }, 287 | ResourceFunc: resources.Permission, 288 | ResourceHandler: resources.PermissionHandler(), 289 | }, 290 | "protectionrule": { 291 | Tools: []ToolRegistration{ 292 | { 293 | Func: tools.ProtectionRuleList, 294 | Handler: tools.ProtectionRuleListHandler(), 295 | }, 296 | { 297 | Func: tools.ProtectionRuleCount, 298 | Handler: tools.ProtectionRuleCountHandler(), 299 | }, 300 | }, 301 | ResourceFunc: resources.ProtectionRule, 302 | ResourceHandler: resources.ProtectionRuleHandler(), 303 | }, 304 | "recoveryplan": { 305 | Tools: []ToolRegistration{ 306 | { 307 | Func: tools.RecoveryPlanList, 308 | Handler: tools.RecoveryPlanListHandler(), 309 | }, 310 | { 311 | Func: tools.RecoveryPlanCount, 312 | Handler: tools.RecoveryPlanCountHandler(), 313 | }, 314 | }, 315 | ResourceFunc: resources.RecoveryPlan, 316 | ResourceHandler: resources.RecoveryPlanHandler(), 317 | }, 318 | "servicegroup": { 319 | Tools: []ToolRegistration{ 320 | { 321 | Func: tools.ServiceGroupList, 322 | Handler: tools.ServiceGroupListHandler(), 323 | }, 324 | { 325 | Func: tools.ServiceGroupCount, 326 | Handler: tools.ServiceGroupCountHandler(), 327 | }, 328 | }, 329 | ResourceFunc: resources.ServiceGroup, 330 | ResourceHandler: resources.ServiceGroupHandler(), 331 | }, 332 | "addressgroup": { 333 | Tools: []ToolRegistration{ 334 | { 335 | Func: tools.AddressGroupList, 336 | Handler: tools.AddressGroupListHandler(), 337 | }, 338 | { 339 | Func: tools.AddressGroupCount, 340 | Handler: tools.AddressGroupCountHandler(), 341 | }, 342 | }, 343 | ResourceFunc: resources.AddressGroup, 344 | ResourceHandler: resources.AddressGroupHandler(), 345 | }, 346 | "recoveryplanjob": { 347 | Tools: []ToolRegistration{ 348 | { 349 | Func: tools.RecoveryPlanJobList, 350 | Handler: tools.RecoveryPlanJobListHandler(), 351 | }, 352 | { 353 | Func: tools.RecoveryPlanJobCount, 354 | Handler: tools.RecoveryPlanJobCountHandler(), 355 | }, 356 | }, 357 | ResourceFunc: resources.RecoveryPlanJob, 358 | ResourceHandler: resources.RecoveryPlanJobHandler(), 359 | }, 360 | } 361 | 362 | // Register all tools and resources 363 | for name, registration := range resourceRegistrations { 364 | // Add all tools 365 | for _, tool := range registration.Tools { 366 | s.AddTool(tool.Func(), tool.Handler) 367 | if debugMode { 368 | fmt.Printf("Registered %s resource and tool\n", name) 369 | } 370 | } 371 | 372 | // Add the resource 373 | s.AddResourceTemplate(registration.ResourceFunc(), registration.ResourceHandler) 374 | } 375 | 376 | // Start the server 377 | if err := server.ServeStdio(s); err != nil { 378 | fmt.Printf("Server error: %v\n", err) 379 | } 380 | } 381 | ```