# 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: -------------------------------------------------------------------------------- ``` bin .DS_Store mcp.json ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown # MCP Nutanix A Model Context Protocol (MCP) server for interacting with Nutanix Prism Central APIs through Large Language Models (LLMs). ## ⚠️ Disclaimer **THIS IS AN EXPERIMENTAL PROJECT** This project was created as a personal project to explore the capabilities of the Model Context Protocol frameworks in Go. It is: - **NOT** an official Nutanix product or tool - **NOT** supported, endorsed, or maintained by Nutanix - **NOT** suitable for production environments - **PROVIDED AS-IS** with no warranties or guarantees **USE AT YOUR OWN RISK**: The author takes no responsibility for any issues, damages, or outages that may result from using this code. ## Overview This MCP server allows LLMs to interact with Nutanix Prism Central by: 1. Connecting to a Prism Central instance with user credentials 2. Listing various resources (VMs, Clusters, Hosts, etc.) 3. Retrieving specific resource details via URI-based access 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. ## Getting Started ### Prerequisites - Go 1.23 or higher - Access to a Nutanix Prism Central instance - Tools like `make` and `go fmt` for building ### Building ```bash # Clone the repository git clone https://github.com/thunderboltsid/mcp-nutanix.git cd mcp-nutanix # Build the MCP server make build ``` ## Credential Configuration The server supports two credential methods: 1. **Interactive credentials** (default) - Works with Claude via MCP prompts 2. **Static credentials** - Required for tools like Cursor that don't support interactive prompts ## MCP Client Configuration To use this server with MCP clients, you need to configure the client to connect to the server. ### Claude Desktop/Code Create or update `~/.anthropic/claude_desktop.json`: ```json { "mcpServers": { "nutanix": { "command": "/path/to/mcp-nutanix" } } } ``` Claude will prompt you for credentials when first using the server. ### Cursor For Cursor, you need to provide static credentials via environment variables since it doesn't support interactive prompts. Create or update `~/.cursor/mcp.json`: ```json { "mcpServers": { "nutanix": { "command": "/path/to/mcp-nutanix", "env": { "NUTANIX_ENDPOINT": "your-prism-central-ip-or-hostname", "NUTANIX_USERNAME": "your-username", "NUTANIX_PASSWORD": "your-password", "NUTANIX_INSECURE": "true" } } } } ``` **Environment Variables:** - `NUTANIX_ENDPOINT` - Prism Central IP or hostname (required) - `NUTANIX_USERNAME` - API username (required) - `NUTANIX_PASSWORD` - API password (required) - `NUTANIX_INSECURE` - Set to "true" for self-signed certificates (optional) ### Other MCP Clients 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. ## Usage 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. ### Resource Listing To list resources, use the appropriate tool: ``` vms clusters hosts images subnets ``` The LLM will receive a JSON list of resources that it can parse and analyze. ### Resource Access To access a specific resource, use a resource URI: ``` vm://{uuid} cluster://{uuid} host://{uuid} ``` The LLM will receive detailed JSON information about the specific resource. ## Development ### Project Structure ``` mcp-nutanix/ ├── bin/ # Compiled binaries ├── internal/ # Internal packages │ ├── client/ # Prism Central client handling │ ├── codegen/ # Code generation utilities │ └── json/ # JSON helpers ├── pkg/ # components │ ├── prompts/ # MCP prompt implementations │ ├── resources/ # Resource handlers │ └── tools/ # Tool handlers └── Makefile # Build and utility commands ``` ### Code Generation The project uses code generation to create resource and tool handlers. To update these: ```bash make generate ``` ## Limitations - Response size is limited by the MCP protocol - Some resources with large response sizes may cause errors - No pagination support in the current implementation - Only supports read operations, no create/update/delete ## License This project is licensed under the MIT License - see the LICENSE file for details. ## Acknowledgments - [Nutanix](https://www.nutanix.com/) for creating the Prism API - [Mark3Labs](https://github.com/mark3labs) for the MCP Go library - [Nutanix Cloud Native](https://github.com/nutanix-cloud-native) for the Prism Go Client ## Contributing This is an experimental project with no formal contribution process. Feel free to create issues or pull requests. ``` -------------------------------------------------------------------------------- /pkg/resources/vm.go: -------------------------------------------------------------------------------- ```go package resources import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // VM defines the VM resource template func VM() mcp.ResourceTemplate { return mcp.NewResourceTemplate( string(ResourceURIPrefix(ResourceTypeVM))+"{uuid}", string(ResourceTypeVM), mcp.WithTemplateDescription("Virtual Machine resource"), mcp.WithTemplateMIMEType("application/json"), ) } // VMHandler implements the handler for the VM resource func VMHandler() server.ResourceTemplateHandlerFunc { return CreateResourceHandler(ResourceTypeVM, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { // Get the VM return client.V3().GetVM(ctx, uuid) }) } ``` -------------------------------------------------------------------------------- /pkg/resources/host.go: -------------------------------------------------------------------------------- ```go package resources import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // Host defines the Host resource template func Host() mcp.ResourceTemplate { return mcp.NewResourceTemplate( string(ResourceURIPrefix(ResourceTypeHost))+"{uuid}", string(ResourceTypeHost), mcp.WithTemplateDescription("Host resource"), mcp.WithTemplateMIMEType("application/json"), ) } // HostHandler implements the handler for the Host resource func HostHandler() server.ResourceTemplateHandlerFunc { return CreateResourceHandler(ResourceTypeHost, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { // Get the Host return client.V3().GetHost(ctx, uuid) }) } ``` -------------------------------------------------------------------------------- /pkg/resources/role.go: -------------------------------------------------------------------------------- ```go package resources import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // Role defines the Role resource template func Role() mcp.ResourceTemplate { return mcp.NewResourceTemplate( string(ResourceURIPrefix(ResourceTypeRole))+"{uuid}", string(ResourceTypeRole), mcp.WithTemplateDescription("Role resource"), mcp.WithTemplateMIMEType("application/json"), ) } // RoleHandler implements the handler for the Role resource func RoleHandler() server.ResourceTemplateHandlerFunc { return CreateResourceHandler(ResourceTypeRole, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { // Get the Role return client.V3().GetRole(ctx, uuid) }) } ``` -------------------------------------------------------------------------------- /pkg/resources/user.go: -------------------------------------------------------------------------------- ```go package resources import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // User defines the User resource template func User() mcp.ResourceTemplate { return mcp.NewResourceTemplate( string(ResourceURIPrefix(ResourceTypeUser))+"{uuid}", string(ResourceTypeUser), mcp.WithTemplateDescription("User resource"), mcp.WithTemplateMIMEType("application/json"), ) } // UserHandler implements the handler for the User resource func UserHandler() server.ResourceTemplateHandlerFunc { return CreateResourceHandler(ResourceTypeUser, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { // Get the User return client.V3().GetUser(ctx, uuid) }) } ``` -------------------------------------------------------------------------------- /pkg/resources/image.go: -------------------------------------------------------------------------------- ```go package resources import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // Image defines the Image resource template func Image() mcp.ResourceTemplate { return mcp.NewResourceTemplate( string(ResourceURIPrefix(ResourceTypeImage))+"{uuid}", string(ResourceTypeImage), mcp.WithTemplateDescription("Image resource"), mcp.WithTemplateMIMEType("application/json"), ) } // ImageHandler implements the handler for the Image resource func ImageHandler() server.ResourceTemplateHandlerFunc { return CreateResourceHandler(ResourceTypeImage, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { // Get the Image return client.V3().GetImage(ctx, uuid) }) } ``` -------------------------------------------------------------------------------- /pkg/resources/subnet.go: -------------------------------------------------------------------------------- ```go package resources import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // Subnet defines the Subnet resource template func Subnet() mcp.ResourceTemplate { return mcp.NewResourceTemplate( string(ResourceURIPrefix(ResourceTypeSubnet))+"{uuid}", string(ResourceTypeSubnet), mcp.WithTemplateDescription("Subnet resource"), mcp.WithTemplateMIMEType("application/json"), ) } // SubnetHandler implements the handler for the Subnet resource func SubnetHandler() server.ResourceTemplateHandlerFunc { return CreateResourceHandler(ResourceTypeSubnet, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { // Get the Subnet return client.V3().GetSubnet(ctx, uuid) }) } ``` -------------------------------------------------------------------------------- /pkg/resources/cluster.go: -------------------------------------------------------------------------------- ```go package resources import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // Cluster defines the Cluster resource template func Cluster() mcp.ResourceTemplate { return mcp.NewResourceTemplate( string(ResourceURIPrefix(ResourceTypeCluster))+"{uuid}", string(ResourceTypeCluster), mcp.WithTemplateDescription("Cluster resource"), mcp.WithTemplateMIMEType("application/json"), ) } // ClusterHandler implements the handler for the Cluster resource func ClusterHandler() server.ResourceTemplateHandlerFunc { return CreateResourceHandler(ResourceTypeCluster, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { // Get the Cluster return client.V3().GetCluster(ctx, uuid) }) } ``` -------------------------------------------------------------------------------- /pkg/resources/project.go: -------------------------------------------------------------------------------- ```go package resources import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // Project defines the Project resource template func Project() mcp.ResourceTemplate { return mcp.NewResourceTemplate( string(ResourceURIPrefix(ResourceTypeProject))+"{uuid}", string(ResourceTypeProject), mcp.WithTemplateDescription("Project resource"), mcp.WithTemplateMIMEType("application/json"), ) } // ProjectHandler implements the handler for the Project resource func ProjectHandler() server.ResourceTemplateHandlerFunc { return CreateResourceHandler(ResourceTypeProject, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { // Get the Project return client.V3().GetProject(ctx, uuid) }) } ``` -------------------------------------------------------------------------------- /pkg/resources/category.go: -------------------------------------------------------------------------------- ```go package resources import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // Category defines the Category resource template func Category() mcp.ResourceTemplate { return mcp.NewResourceTemplate( string(ResourceURIPrefix(ResourceTypeCategory))+"{uuid}", string(ResourceTypeCategory), mcp.WithTemplateDescription("Category resource"), mcp.WithTemplateMIMEType("application/json"), ) } // CategoryHandler implements the handler for the Category resource func CategoryHandler() server.ResourceTemplateHandlerFunc { return CreateResourceHandler(ResourceTypeCategory, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { // Get the Category return client.V3().GetCategoryKey(ctx, uuid) }) } ``` -------------------------------------------------------------------------------- /pkg/resources/usergroup.go: -------------------------------------------------------------------------------- ```go package resources import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // UserGroup defines the UserGroup resource template func UserGroup() mcp.ResourceTemplate { return mcp.NewResourceTemplate( string(ResourceURIPrefix(ResourceTypeUserGroup))+"{uuid}", string(ResourceTypeUserGroup), mcp.WithTemplateDescription("User Group resource"), mcp.WithTemplateMIMEType("application/json"), ) } // UserGroupHandler implements the handler for the UserGroup resource func UserGroupHandler() server.ResourceTemplateHandlerFunc { return CreateResourceHandler(ResourceTypeUserGroup, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { // Get the UserGroup return client.V3().GetUserGroup(ctx, uuid) }) } ``` -------------------------------------------------------------------------------- /pkg/resources/permission.go: -------------------------------------------------------------------------------- ```go package resources import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // Permission defines the Permission resource template func Permission() mcp.ResourceTemplate { return mcp.NewResourceTemplate( string(ResourceURIPrefix(ResourceTypePermission))+"{uuid}", string(ResourceTypePermission), mcp.WithTemplateDescription("Permission resource"), mcp.WithTemplateMIMEType("application/json"), ) } // PermissionHandler implements the handler for the Permission resource func PermissionHandler() server.ResourceTemplateHandlerFunc { return CreateResourceHandler(ResourceTypePermission, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { // Get the Permission return client.V3().GetPermission(ctx, uuid) }) } ``` -------------------------------------------------------------------------------- /pkg/resources/volumegroup.go: -------------------------------------------------------------------------------- ```go package resources import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // VolumeGroup defines the VolumeGroup resource template func VolumeGroup() mcp.ResourceTemplate { return mcp.NewResourceTemplate( string(ResourceURIPrefix(ResourceTypeVolumeGroup))+"{uuid}", string(ResourceTypeVolumeGroup), mcp.WithTemplateDescription("Volume Group resource"), mcp.WithTemplateMIMEType("application/json"), ) } // VolumeGroupHandler implements the handler for the VolumeGroup resource func VolumeGroupHandler() server.ResourceTemplateHandlerFunc { return CreateResourceHandler(ResourceTypeVolumeGroup, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { // Get the VolumeGroup return client.V3().GetVolumeGroup(ctx, uuid) }) } ``` -------------------------------------------------------------------------------- /pkg/resources/addressgroup.go: -------------------------------------------------------------------------------- ```go package resources import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // AddressGroup defines the AddressGroup resource template func AddressGroup() mcp.ResourceTemplate { return mcp.NewResourceTemplate( string(ResourceURIPrefix(ResourceTypeAddressGroup))+"{uuid}", string(ResourceTypeAddressGroup), mcp.WithTemplateDescription("Address Group resource"), mcp.WithTemplateMIMEType("application/json"), ) } // AddressGroupHandler implements the handler for the AddressGroup resource func AddressGroupHandler() server.ResourceTemplateHandlerFunc { return CreateResourceHandler(ResourceTypeAddressGroup, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { // Get the AddressGroup return client.V3().GetAddressGroup(ctx, uuid) }) } ``` -------------------------------------------------------------------------------- /pkg/resources/recoveryplan.go: -------------------------------------------------------------------------------- ```go package resources import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // RecoveryPlan defines the RecoveryPlan resource template func RecoveryPlan() mcp.ResourceTemplate { return mcp.NewResourceTemplate( string(ResourceURIPrefix(ResourceTypeRecoveryPlan))+"{uuid}", string(ResourceTypeRecoveryPlan), mcp.WithTemplateDescription("Recovery Plan resource"), mcp.WithTemplateMIMEType("application/json"), ) } // RecoveryPlanHandler implements the handler for the RecoveryPlan resource func RecoveryPlanHandler() server.ResourceTemplateHandlerFunc { return CreateResourceHandler(ResourceTypeRecoveryPlan, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { // Get the RecoveryPlan return client.V3().GetRecoveryPlan(ctx, uuid) }) } ``` -------------------------------------------------------------------------------- /pkg/resources/servicegroup.go: -------------------------------------------------------------------------------- ```go package resources import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // ServiceGroup defines the ServiceGroup resource template func ServiceGroup() mcp.ResourceTemplate { return mcp.NewResourceTemplate( string(ResourceURIPrefix(ResourceTypeServiceGroup))+"{uuid}", string(ResourceTypeServiceGroup), mcp.WithTemplateDescription("Service Group resource"), mcp.WithTemplateMIMEType("application/json"), ) } // ServiceGroupHandler implements the handler for the ServiceGroup resource func ServiceGroupHandler() server.ResourceTemplateHandlerFunc { return CreateResourceHandler(ResourceTypeServiceGroup, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { // Get the ServiceGroup return client.V3().GetServiceGroup(ctx, uuid) }) } ``` -------------------------------------------------------------------------------- /internal/codegen/cmd/main.go: -------------------------------------------------------------------------------- ```go package main import ( "flag" "fmt" "os" "path/filepath" "github.com/thunderboltsid/mcp-nutanix/internal/codegen/templates" ) func main() { // Parse command-line arguments outputDir := flag.String("output", ".", "Output directory for generated files") flag.Parse() // Get absolute path absPath, err := filepath.Abs(*outputDir) if err != nil { fmt.Printf("Error getting absolute path: %v\n", err) os.Exit(1) } fmt.Printf("Generating resource and tool files in: %s\n", absPath) // Generate all resource files if err := templates.GenerateResourceFiles(absPath); err != nil { fmt.Printf("Error generating files: %v\n", err) os.Exit(1) } // Generate all resource files if err := templates.GenerateToolFiles(absPath); err != nil { fmt.Printf("Error generating files: %v\n", err) os.Exit(1) } fmt.Println("Resource and tool files generated successfully!") } ``` -------------------------------------------------------------------------------- /pkg/resources/protectionrule.go: -------------------------------------------------------------------------------- ```go package resources import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // ProtectionRule defines the ProtectionRule resource template func ProtectionRule() mcp.ResourceTemplate { return mcp.NewResourceTemplate( string(ResourceURIPrefix(ResourceTypeProtectionRule))+"{uuid}", string(ResourceTypeProtectionRule), mcp.WithTemplateDescription("Protection Rule resource"), mcp.WithTemplateMIMEType("application/json"), ) } // ProtectionRuleHandler implements the handler for the ProtectionRule resource func ProtectionRuleHandler() server.ResourceTemplateHandlerFunc { return CreateResourceHandler(ResourceTypeProtectionRule, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { // Get the ProtectionRule return client.V3().GetProtectionRule(ctx, uuid) }) } ``` -------------------------------------------------------------------------------- /pkg/resources/recoveryplanjob.go: -------------------------------------------------------------------------------- ```go package resources import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // RecoveryPlanJob defines the RecoveryPlanJob resource template func RecoveryPlanJob() mcp.ResourceTemplate { return mcp.NewResourceTemplate( string(ResourceURIPrefix(ResourceTypeRecoveryPlanJob))+"{uuid}", string(ResourceTypeRecoveryPlanJob), mcp.WithTemplateDescription("Recovery Plan Job resource"), mcp.WithTemplateMIMEType("application/json"), ) } // RecoveryPlanJobHandler implements the handler for the RecoveryPlanJob resource func RecoveryPlanJobHandler() server.ResourceTemplateHandlerFunc { return CreateResourceHandler(ResourceTypeRecoveryPlanJob, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { // Get the RecoveryPlanJob return client.V3().GetRecoveryPlanJob(ctx, uuid) }) } ``` -------------------------------------------------------------------------------- /pkg/resources/availabilityzone.go: -------------------------------------------------------------------------------- ```go package resources import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // AvailabilityZone defines the AvailabilityZone resource template func AvailabilityZone() mcp.ResourceTemplate { return mcp.NewResourceTemplate( string(ResourceURIPrefix(ResourceTypeAvailabilityZone))+"{uuid}", string(ResourceTypeAvailabilityZone), mcp.WithTemplateDescription("Availability Zone resource"), mcp.WithTemplateMIMEType("application/json"), ) } // AvailabilityZoneHandler implements the handler for the AvailabilityZone resource func AvailabilityZoneHandler() server.ResourceTemplateHandlerFunc { return CreateResourceHandler(ResourceTypeAvailabilityZone, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { // Get the AvailabilityZone return client.V3().GetAvailabilityZone(ctx, uuid) }) } ``` -------------------------------------------------------------------------------- /pkg/resources/accesscontrolpolicy.go: -------------------------------------------------------------------------------- ```go package resources import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // AccessControlPolicy defines the AccessControlPolicy resource template func AccessControlPolicy() mcp.ResourceTemplate { return mcp.NewResourceTemplate( string(ResourceURIPrefix(ResourceTypeAccessControlPolicy))+"{uuid}", string(ResourceTypeAccessControlPolicy), mcp.WithTemplateDescription("Access Control Policy resource"), mcp.WithTemplateMIMEType("application/json"), ) } // AccessControlPolicyHandler implements the handler for the AccessControlPolicy resource func AccessControlPolicyHandler() server.ResourceTemplateHandlerFunc { return CreateResourceHandler(ResourceTypeAccessControlPolicy, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { // Get the AccessControlPolicy return client.V3().GetAccessControlPolicy(ctx, uuid) }) } ``` -------------------------------------------------------------------------------- /pkg/resources/networksecurityrule.go: -------------------------------------------------------------------------------- ```go package resources import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // NetworkSecurityRule defines the NetworkSecurityRule resource template func NetworkSecurityRule() mcp.ResourceTemplate { return mcp.NewResourceTemplate( string(ResourceURIPrefix(ResourceTypeNetworkSecurityRule))+"{uuid}", string(ResourceTypeNetworkSecurityRule), mcp.WithTemplateDescription("Network Security Rule resource"), mcp.WithTemplateMIMEType("application/json"), ) } // NetworkSecurityRuleHandler implements the handler for the NetworkSecurityRule resource func NetworkSecurityRuleHandler() server.ResourceTemplateHandlerFunc { return CreateResourceHandler(ResourceTypeNetworkSecurityRule, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { // Get the NetworkSecurityRule return client.V3().GetNetworkSecurityRule(ctx, uuid) }) } ``` -------------------------------------------------------------------------------- /pkg/tools/apinamespace.go: -------------------------------------------------------------------------------- ```go package tools import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/thunderboltsid/mcp-nutanix/internal/json" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // ApiNamespacesList defines the API namespaces list tool func ApiNamespacesList() mcp.Tool { return mcp.NewTool("api_namespaces_list", mcp.WithDescription("List available API namespaces and their routes in Prism Central"), ) } // ApiNamespacesListHandler implements the handler for the API namespaces list tool func ApiNamespacesListHandler() server.ToolHandlerFunc { return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Get the Prism client prismClient := client.GetPrismClient() // Call the Actuator API to get version routes response, err := prismClient.V4().ActuatorApiInstance.GetVersionRoutes(ctx) if err != nil { return nil, err } // Convert to JSON using regular JSON encoder cjson := json.RegularJSONEncoder(response) jsonBytes, err := cjson.MarshalJSON() if err != nil { return nil, err } return mcp.NewToolResultText(string(jsonBytes)), nil } } ``` -------------------------------------------------------------------------------- /internal/client/modelcontextclient.go: -------------------------------------------------------------------------------- ```go package client import ( "errors" "sync" "github.com/nutanix-cloud-native/prism-go-client/environment/providers/mcp" ) // mcpModelContextClient is an example implementation of ModelContextClient. // It stores key–value pairs in an in-memory map. type mcpModelContextClient struct { mu sync.RWMutex data map[string]string } var PrismClientProvider = &mcpModelContextClient{ data: make(map[string]string), } var _ mcp.ModelContextClient = &mcpModelContextClient{} // NewMCPModelContextClient creates a new instance with the provided initial data. func NewMCPModelContextClient(initialData map[string]string) mcp.ModelContextClient { if initialData == nil { initialData = make(map[string]string) } return &mcpModelContextClient{ data: initialData, } } // GetValue retrieves the value for a given key from the model context. // It returns an error if the key is not found. func (c *mcpModelContextClient) GetValue(key string) (string, error) { c.mu.RLock() defer c.mu.RUnlock() if val, exists := c.data[key]; exists { return val, nil } return "", errors.New("model context key not found") } // Optionally, you can add a method to update values in the model context. func (c *mcpModelContextClient) UpdateValue(key, value string) { c.mu.Lock() defer c.mu.Unlock() c.data[key] = value } ``` -------------------------------------------------------------------------------- /internal/json/marshal_test.go: -------------------------------------------------------------------------------- ```go package json import ( "encoding/json" "github.com/stretchr/testify/assert" "testing" ) func TestMarshalJSON(t *testing.T) { jsonData := []byte(` { "api_version": "3.0", "entities": [ { "metadata": {"uuid": "b7816400-16c5-47c7-9fcc-474e39594ad5"}, "status": {"big": "object"}, "spec": { "name": "vm1", "resources": { "guest_customization": {"very": "large", "nested": "object"}, "subnets": [{"name": "subnet1"}, {"name": "subnet2"}] } } }, { "metadata": {"uuid": "b7816400-16c5-47c7-9fcc-474e39594ad6"}, "status": {"big": "object2"}, "spec": { "name": "vm2", "resources": { "guest_customization": {"very": "large", "nested": "object"}, "subnets": [{"name": "subnet1"}, {"name": "subnet2"}] } } } ] }`) ujson := make(map[string]any) err := json.Unmarshal(jsonData, &ujson) assert.NoError(t, err) cjson := CustomJSON{ Value: ujson, StripPaths: []string{ "api_version", "entities[].status", "spec.resources.guest_customization", "entities[].spec.resources.guest_customization", }, } mdata, err := json.Marshal(cjson) assert.NoError(t, err) assert.NotNil(t, mdata) } ``` -------------------------------------------------------------------------------- /pkg/tools/vm.go: -------------------------------------------------------------------------------- ```go package tools import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/thunderboltsid/mcp-nutanix/pkg/resources" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // VM defines the VM tool func VMList() mcp.Tool { return mcp.NewTool("vm_list", mcp.WithDescription("List vm resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // VMListHandler implements the handler for the VM list tool func VMListHandler() server.ToolHandlerFunc { return CreateListToolHandler( resources.ResourceTypeVM, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Use ListAll function to get all resources return client.V3().ListAllVM(ctx, "") }, ) } // VMCount defines the VM count tool func VMCount() mcp.Tool { return mcp.NewTool("vm_count", mcp.WithDescription("Count vm resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // VMCountHandler implements the handler for the VM count tool func VMCountHandler() server.ToolHandlerFunc { return CreateCountToolHandler( resources.ResourceTypeVM, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Use ListAll function to get all resources resp, err := client.V3().ListAllVM(ctx, "") if err != nil { return nil, err } res := map[string]interface{}{ "resource_type": "VM", "count": len(resp.Entities), "metadata": resp.Metadata, } return res, nil }, ) } ``` -------------------------------------------------------------------------------- /pkg/tools/role.go: -------------------------------------------------------------------------------- ```go package tools import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/thunderboltsid/mcp-nutanix/pkg/resources" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // Role defines the Role tool func RoleList() mcp.Tool { return mcp.NewTool("role_list", mcp.WithDescription("List role resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // RoleListHandler implements the handler for the Role list tool func RoleListHandler() server.ToolHandlerFunc { return CreateListToolHandler( resources.ResourceTypeRole, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Use ListAll function to get all resources return client.V3().ListAllRole(ctx, "") }, ) } // RoleCount defines the Role count tool func RoleCount() mcp.Tool { return mcp.NewTool("role_count", mcp.WithDescription("Count role resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // RoleCountHandler implements the handler for the Role count tool func RoleCountHandler() server.ToolHandlerFunc { return CreateCountToolHandler( resources.ResourceTypeRole, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Use ListAll function to get all resources resp, err := client.V3().ListAllRole(ctx, "") if err != nil { return nil, err } res := map[string]interface{}{ "resource_type": "Role", "count": len(resp.Entities), "metadata": resp.Metadata, } return res, nil }, ) } ``` -------------------------------------------------------------------------------- /pkg/tools/user.go: -------------------------------------------------------------------------------- ```go package tools import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/thunderboltsid/mcp-nutanix/pkg/resources" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // User defines the User tool func UserList() mcp.Tool { return mcp.NewTool("user_list", mcp.WithDescription("List user resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // UserListHandler implements the handler for the User list tool func UserListHandler() server.ToolHandlerFunc { return CreateListToolHandler( resources.ResourceTypeUser, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Use ListAll function to get all resources return client.V3().ListAllUser(ctx, "") }, ) } // UserCount defines the User count tool func UserCount() mcp.Tool { return mcp.NewTool("user_count", mcp.WithDescription("Count user resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // UserCountHandler implements the handler for the User count tool func UserCountHandler() server.ToolHandlerFunc { return CreateCountToolHandler( resources.ResourceTypeUser, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Use ListAll function to get all resources resp, err := client.V3().ListAllUser(ctx, "") if err != nil { return nil, err } res := map[string]interface{}{ "resource_type": "User", "count": len(resp.Entities), "metadata": resp.Metadata, } return res, nil }, ) } ``` -------------------------------------------------------------------------------- /pkg/tools/host.go: -------------------------------------------------------------------------------- ```go package tools import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/thunderboltsid/mcp-nutanix/pkg/resources" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // Host defines the Host tool func HostList() mcp.Tool { return mcp.NewTool("host_list", mcp.WithDescription("List host resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // HostListHandler implements the handler for the Host list tool func HostListHandler() server.ToolHandlerFunc { return CreateListToolHandler( resources.ResourceTypeHost, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Special case for Host which doesn't take a filter return client.V3().ListAllHost(ctx) }, ) } // HostCount defines the Host count tool func HostCount() mcp.Tool { return mcp.NewTool("host_count", mcp.WithDescription("Count host resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // HostCountHandler implements the handler for the Host count tool func HostCountHandler() server.ToolHandlerFunc { return CreateCountToolHandler( resources.ResourceTypeHost, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Special case for Host which doesn't take a filter resp, err := client.V3().ListAllHost(ctx) if err != nil { return nil, err } res := map[string]interface{}{ "resource_type": "Host", "count": len(resp.Entities), "metadata": resp.Metadata, } return res, nil }, ) } ``` -------------------------------------------------------------------------------- /pkg/tools/image.go: -------------------------------------------------------------------------------- ```go package tools import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/thunderboltsid/mcp-nutanix/pkg/resources" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // Image defines the Image tool func ImageList() mcp.Tool { return mcp.NewTool("image_list", mcp.WithDescription("List image resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // ImageListHandler implements the handler for the Image list tool func ImageListHandler() server.ToolHandlerFunc { return CreateListToolHandler( resources.ResourceTypeImage, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Use ListAll function to get all resources return client.V3().ListAllImage(ctx, "") }, ) } // ImageCount defines the Image count tool func ImageCount() mcp.Tool { return mcp.NewTool("image_count", mcp.WithDescription("Count image resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // ImageCountHandler implements the handler for the Image count tool func ImageCountHandler() server.ToolHandlerFunc { return CreateCountToolHandler( resources.ResourceTypeImage, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Use ListAll function to get all resources resp, err := client.V3().ListAllImage(ctx, "") if err != nil { return nil, err } res := map[string]interface{}{ "resource_type": "Image", "count": len(resp.Entities), "metadata": resp.Metadata, } return res, nil }, ) } ``` -------------------------------------------------------------------------------- /pkg/tools/cluster.go: -------------------------------------------------------------------------------- ```go package tools import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/thunderboltsid/mcp-nutanix/pkg/resources" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // Cluster defines the Cluster tool func ClusterList() mcp.Tool { return mcp.NewTool("cluster_list", mcp.WithDescription("List cluster resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // ClusterListHandler implements the handler for the Cluster list tool func ClusterListHandler() server.ToolHandlerFunc { return CreateListToolHandler( resources.ResourceTypeCluster, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Use ListAll function to get all resources return client.V3().ListAllCluster(ctx, "") }, ) } // ClusterCount defines the Cluster count tool func ClusterCount() mcp.Tool { return mcp.NewTool("cluster_count", mcp.WithDescription("Count cluster resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // ClusterCountHandler implements the handler for the Cluster count tool func ClusterCountHandler() server.ToolHandlerFunc { return CreateCountToolHandler( resources.ResourceTypeCluster, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Use ListAll function to get all resources resp, err := client.V3().ListAllCluster(ctx, "") if err != nil { return nil, err } res := map[string]interface{}{ "resource_type": "Cluster", "count": len(resp.Entities), "metadata": resp.Metadata, } return res, nil }, ) } ``` -------------------------------------------------------------------------------- /pkg/tools/project.go: -------------------------------------------------------------------------------- ```go package tools import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/thunderboltsid/mcp-nutanix/pkg/resources" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // Project defines the Project tool func ProjectList() mcp.Tool { return mcp.NewTool("project_list", mcp.WithDescription("List project resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // ProjectListHandler implements the handler for the Project list tool func ProjectListHandler() server.ToolHandlerFunc { return CreateListToolHandler( resources.ResourceTypeProject, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Use ListAll function to get all resources return client.V3().ListAllProject(ctx, "") }, ) } // ProjectCount defines the Project count tool func ProjectCount() mcp.Tool { return mcp.NewTool("project_count", mcp.WithDescription("Count project resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // ProjectCountHandler implements the handler for the Project count tool func ProjectCountHandler() server.ToolHandlerFunc { return CreateCountToolHandler( resources.ResourceTypeProject, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Use ListAll function to get all resources resp, err := client.V3().ListAllProject(ctx, "") if err != nil { return nil, err } res := map[string]interface{}{ "resource_type": "Project", "count": len(resp.Entities), "metadata": resp.Metadata, } return res, nil }, ) } ``` -------------------------------------------------------------------------------- /pkg/tools/subnet.go: -------------------------------------------------------------------------------- ```go package tools import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/thunderboltsid/mcp-nutanix/pkg/resources" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // Subnet defines the Subnet tool func SubnetList() mcp.Tool { return mcp.NewTool("subnet_list", mcp.WithDescription("List subnet resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // SubnetListHandler implements the handler for the Subnet list tool func SubnetListHandler() server.ToolHandlerFunc { return CreateListToolHandler( resources.ResourceTypeSubnet, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Special case for Subnet which has an extra parameter return client.V3().ListAllSubnet(ctx, "", nil) }, ) } // SubnetCount defines the Subnet count tool func SubnetCount() mcp.Tool { return mcp.NewTool("subnet_count", mcp.WithDescription("Count subnet resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // SubnetCountHandler implements the handler for the Subnet count tool func SubnetCountHandler() server.ToolHandlerFunc { return CreateCountToolHandler( resources.ResourceTypeSubnet, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Special case for Subnet which has an extra parameter resp, err := client.V3().ListAllSubnet(ctx, "", nil) if err != nil { return nil, err } res := map[string]interface{}{ "resource_type": "Subnet", "count": len(resp.Entities), "metadata": resp.Metadata, } return res, nil }, ) } ``` -------------------------------------------------------------------------------- /pkg/tools/usergroup.go: -------------------------------------------------------------------------------- ```go package tools import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/thunderboltsid/mcp-nutanix/pkg/resources" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // UserGroup defines the UserGroup tool func UserGroupList() mcp.Tool { return mcp.NewTool("usergroup_list", mcp.WithDescription("List usergroup resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // UserGroupListHandler implements the handler for the UserGroup list tool func UserGroupListHandler() server.ToolHandlerFunc { return CreateListToolHandler( resources.ResourceTypeUserGroup, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Use ListAll function to get all resources return client.V3().ListAllUserGroup(ctx, "") }, ) } // UserGroupCount defines the UserGroup count tool func UserGroupCount() mcp.Tool { return mcp.NewTool("usergroup_count", mcp.WithDescription("Count usergroup resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // UserGroupCountHandler implements the handler for the UserGroup count tool func UserGroupCountHandler() server.ToolHandlerFunc { return CreateCountToolHandler( resources.ResourceTypeUserGroup, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Use ListAll function to get all resources resp, err := client.V3().ListAllUserGroup(ctx, "") if err != nil { return nil, err } res := map[string]interface{}{ "resource_type": "UserGroup", "count": len(resp.Entities), "metadata": resp.Metadata, } return res, nil }, ) } ``` -------------------------------------------------------------------------------- /pkg/tools/permission.go: -------------------------------------------------------------------------------- ```go package tools import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/thunderboltsid/mcp-nutanix/pkg/resources" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // Permission defines the Permission tool func PermissionList() mcp.Tool { return mcp.NewTool("permission_list", mcp.WithDescription("List permission resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // PermissionListHandler implements the handler for the Permission list tool func PermissionListHandler() server.ToolHandlerFunc { return CreateListToolHandler( resources.ResourceTypePermission, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Use ListAll function to get all resources return client.V3().ListAllPermission(ctx, "") }, ) } // PermissionCount defines the Permission count tool func PermissionCount() mcp.Tool { return mcp.NewTool("permission_count", mcp.WithDescription("Count permission resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // PermissionCountHandler implements the handler for the Permission count tool func PermissionCountHandler() server.ToolHandlerFunc { return CreateCountToolHandler( resources.ResourceTypePermission, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Use ListAll function to get all resources resp, err := client.V3().ListAllPermission(ctx, "") if err != nil { return nil, err } res := map[string]interface{}{ "resource_type": "Permission", "count": len(resp.Entities), "metadata": resp.Metadata, } return res, nil }, ) } ``` -------------------------------------------------------------------------------- /pkg/tools/addressgroup.go: -------------------------------------------------------------------------------- ```go package tools import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/thunderboltsid/mcp-nutanix/pkg/resources" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // AddressGroup defines the AddressGroup tool func AddressGroupList() mcp.Tool { return mcp.NewTool("addressgroup_list", mcp.WithDescription("List addressgroup resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // AddressGroupListHandler implements the handler for the AddressGroup list tool func AddressGroupListHandler() server.ToolHandlerFunc { return CreateListToolHandler( resources.ResourceTypeAddressGroup, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Use ListAll function to get all resources return client.V3().ListAllAddressGroups(ctx, "") }, ) } // AddressGroupCount defines the AddressGroup count tool func AddressGroupCount() mcp.Tool { return mcp.NewTool("addressgroup_count", mcp.WithDescription("Count addressgroup resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // AddressGroupCountHandler implements the handler for the AddressGroup count tool func AddressGroupCountHandler() server.ToolHandlerFunc { return CreateCountToolHandler( resources.ResourceTypeAddressGroup, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Use ListAll function to get all resources resp, err := client.V3().ListAllAddressGroups(ctx, "") if err != nil { return nil, err } res := map[string]interface{}{ "resource_type": "AddressGroup", "count": len(resp.Entities), "metadata": resp.Metadata, } return res, nil }, ) } ``` -------------------------------------------------------------------------------- /pkg/tools/recoveryplan.go: -------------------------------------------------------------------------------- ```go package tools import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/thunderboltsid/mcp-nutanix/pkg/resources" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // RecoveryPlan defines the RecoveryPlan tool func RecoveryPlanList() mcp.Tool { return mcp.NewTool("recoveryplan_list", mcp.WithDescription("List recoveryplan resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // RecoveryPlanListHandler implements the handler for the RecoveryPlan list tool func RecoveryPlanListHandler() server.ToolHandlerFunc { return CreateListToolHandler( resources.ResourceTypeRecoveryPlan, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Use ListAll function to get all resources return client.V3().ListAllRecoveryPlans(ctx, "") }, ) } // RecoveryPlanCount defines the RecoveryPlan count tool func RecoveryPlanCount() mcp.Tool { return mcp.NewTool("recoveryplan_count", mcp.WithDescription("Count recoveryplan resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // RecoveryPlanCountHandler implements the handler for the RecoveryPlan count tool func RecoveryPlanCountHandler() server.ToolHandlerFunc { return CreateCountToolHandler( resources.ResourceTypeRecoveryPlan, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Use ListAll function to get all resources resp, err := client.V3().ListAllRecoveryPlans(ctx, "") if err != nil { return nil, err } res := map[string]interface{}{ "resource_type": "RecoveryPlan", "count": len(resp.Entities), "metadata": resp.Metadata, } return res, nil }, ) } ``` -------------------------------------------------------------------------------- /pkg/tools/servicegroup.go: -------------------------------------------------------------------------------- ```go package tools import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/thunderboltsid/mcp-nutanix/pkg/resources" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // ServiceGroup defines the ServiceGroup tool func ServiceGroupList() mcp.Tool { return mcp.NewTool("servicegroup_list", mcp.WithDescription("List servicegroup resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // ServiceGroupListHandler implements the handler for the ServiceGroup list tool func ServiceGroupListHandler() server.ToolHandlerFunc { return CreateListToolHandler( resources.ResourceTypeServiceGroup, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Use ListAll function to get all resources return client.V3().ListAllServiceGroups(ctx, "") }, ) } // ServiceGroupCount defines the ServiceGroup count tool func ServiceGroupCount() mcp.Tool { return mcp.NewTool("servicegroup_count", mcp.WithDescription("Count servicegroup resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // ServiceGroupCountHandler implements the handler for the ServiceGroup count tool func ServiceGroupCountHandler() server.ToolHandlerFunc { return CreateCountToolHandler( resources.ResourceTypeServiceGroup, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Use ListAll function to get all resources resp, err := client.V3().ListAllServiceGroups(ctx, "") if err != nil { return nil, err } res := map[string]interface{}{ "resource_type": "ServiceGroup", "count": len(resp.Entities), "metadata": resp.Metadata, } return res, nil }, ) } ``` -------------------------------------------------------------------------------- /pkg/tools/protectionrule.go: -------------------------------------------------------------------------------- ```go package tools import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/thunderboltsid/mcp-nutanix/pkg/resources" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // ProtectionRule defines the ProtectionRule tool func ProtectionRuleList() mcp.Tool { return mcp.NewTool("protectionrule_list", mcp.WithDescription("List protectionrule resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // ProtectionRuleListHandler implements the handler for the ProtectionRule list tool func ProtectionRuleListHandler() server.ToolHandlerFunc { return CreateListToolHandler( resources.ResourceTypeProtectionRule, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Use ListAll function to get all resources return client.V3().ListAllProtectionRules(ctx, "") }, ) } // ProtectionRuleCount defines the ProtectionRule count tool func ProtectionRuleCount() mcp.Tool { return mcp.NewTool("protectionrule_count", mcp.WithDescription("Count protectionrule resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // ProtectionRuleCountHandler implements the handler for the ProtectionRule count tool func ProtectionRuleCountHandler() server.ToolHandlerFunc { return CreateCountToolHandler( resources.ResourceTypeProtectionRule, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Use ListAll function to get all resources resp, err := client.V3().ListAllProtectionRules(ctx, "") if err != nil { return nil, err } res := map[string]interface{}{ "resource_type": "ProtectionRule", "count": len(resp.Entities), "metadata": resp.Metadata, } return res, nil }, ) } ``` -------------------------------------------------------------------------------- /pkg/tools/volumegroup.go: -------------------------------------------------------------------------------- ```go package tools import ( "context" v3 "github.com/nutanix-cloud-native/prism-go-client/v3" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/thunderboltsid/mcp-nutanix/pkg/resources" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // VolumeGroup defines the VolumeGroup tool func VolumeGroupList() mcp.Tool { return mcp.NewTool("volumegroup_list", mcp.WithDescription("List volumegroup resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // VolumeGroupListHandler implements the handler for the VolumeGroup list tool func VolumeGroupListHandler() server.ToolHandlerFunc { return CreateListToolHandler( resources.ResourceTypeVolumeGroup, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Create DSMetadata without filter metadata := &v3.DSMetadata{} return client.V3().ListVolumeGroup(ctx, metadata) }, ) } // VolumeGroupCount defines the VolumeGroup count tool func VolumeGroupCount() mcp.Tool { return mcp.NewTool("volumegroup_count", mcp.WithDescription("Count volumegroup resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // VolumeGroupCountHandler implements the handler for the VolumeGroup count tool func VolumeGroupCountHandler() server.ToolHandlerFunc { return CreateCountToolHandler( resources.ResourceTypeVolumeGroup, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Create DSMetadata without filter metadata := &v3.DSMetadata{} resp, err := client.V3().ListVolumeGroup(ctx, metadata) if err != nil { return nil, err } res := map[string]interface{}{ "resource_type": "VolumeGroup", "count": len(resp.Entities), "metadata": resp.Metadata, } return res, nil }, ) } ``` -------------------------------------------------------------------------------- /pkg/tools/category.go: -------------------------------------------------------------------------------- ```go package tools import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/thunderboltsid/mcp-nutanix/pkg/resources" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" v3 "github.com/nutanix-cloud-native/prism-go-client/v3" ) // Category defines the Category tool func CategoryList() mcp.Tool { return mcp.NewTool("category_list", mcp.WithDescription("List category resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // CategoryListHandler implements the handler for the Category list tool func CategoryListHandler() server.ToolHandlerFunc { return CreateListToolHandler( resources.ResourceTypeCategory, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Special case for Category which takes CategoryListMetadata metadata := &v3.CategoryListMetadata{} return client.V3().ListCategories(ctx, metadata) }, ) } // CategoryCount defines the Category count tool func CategoryCount() mcp.Tool { return mcp.NewTool("category_count", mcp.WithDescription("Count category resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // CategoryCountHandler implements the handler for the Category count tool func CategoryCountHandler() server.ToolHandlerFunc { return CreateCountToolHandler( resources.ResourceTypeCategory, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Special case for Category which takes CategoryListMetadata metadata := &v3.CategoryListMetadata{} resp, err := client.V3().ListCategories(ctx, metadata) if err != nil { return nil, err } res := map[string]interface{}{ "resource_type": "Category", "count": len(resp.Entities), "metadata": resp.Metadata, } return res, nil }, ) } ``` -------------------------------------------------------------------------------- /internal/client/client.go: -------------------------------------------------------------------------------- ```go package client import ( "github.com/nutanix-cloud-native/prism-go-client/environment" "github.com/nutanix-cloud-native/prism-go-client/environment/providers/local" "github.com/nutanix-cloud-native/prism-go-client/environment/providers/mcp" envtypes "github.com/nutanix-cloud-native/prism-go-client/environment/types" prismclientv3 "github.com/nutanix-cloud-native/prism-go-client/v3" prismclientv4 "github.com/nutanix-cloud-native/prism-go-client/v4" "k8s.io/klog" ) var ( prismClient *NutanixClient ) func Init(modelcontextclient mcp.ModelContextClient) { prismClient = &NutanixClient{ env: environment.NewEnvironment(local.NewProvider(), mcp.NewProvider(modelcontextclient)), v3ClientCache: prismclientv3.NewClientCache(), v4ClientCache: prismclientv4.NewClientCache(), } } func GetPrismClient() *NutanixClient { if prismClient == nil { panic("Prism client not initialized. Call Init() first.") } return prismClient } type NutanixClient struct { env envtypes.Environment v3ClientCache *prismclientv3.ClientCache v4ClientCache *prismclientv4.ClientCache } // GetV3Client returns the v3 client func (n *NutanixClient) V3() prismclientv3.Service { c, err := n.v3ClientCache.GetOrCreate(n) if err != nil { panic(err) } return c.V3 } // GetV4Client returns the v4 client func (n *NutanixClient) V4() *prismclientv4.Client { c, err := n.v4ClientCache.GetOrCreate(n) if err != nil { panic(err) } return c } // Key returns the constant client name // This implements the CachedClientParams interface of prism-go-client func (n *NutanixClient) Key() string { return "mcp-server" } // ManagementEndpoint returns the management endpoint of the Nutanix cluster // This implements the CachedClientParams interface of prism-go-client func (n *NutanixClient) ManagementEndpoint() envtypes.ManagementEndpoint { mgmtEndpoint, err := n.env.GetManagementEndpoint(envtypes.Topology{}) if err != nil { klog.Errorf("failed to get management endpoint: %s", err.Error()) return envtypes.ManagementEndpoint{} } return *mgmtEndpoint } ``` -------------------------------------------------------------------------------- /pkg/tools/accesscontrolpolicy.go: -------------------------------------------------------------------------------- ```go package tools import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/thunderboltsid/mcp-nutanix/pkg/resources" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // AccessControlPolicy defines the AccessControlPolicy tool func AccessControlPolicyList() mcp.Tool { return mcp.NewTool("accesscontrolpolicy_list", mcp.WithDescription("List accesscontrolpolicy resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // AccessControlPolicyListHandler implements the handler for the AccessControlPolicy list tool func AccessControlPolicyListHandler() server.ToolHandlerFunc { return CreateListToolHandler( resources.ResourceTypeAccessControlPolicy, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Use ListAll function to get all resources return client.V3().ListAllAccessControlPolicy(ctx, "") }, ) } // AccessControlPolicyCount defines the AccessControlPolicy count tool func AccessControlPolicyCount() mcp.Tool { return mcp.NewTool("accesscontrolpolicy_count", mcp.WithDescription("Count accesscontrolpolicy resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // AccessControlPolicyCountHandler implements the handler for the AccessControlPolicy count tool func AccessControlPolicyCountHandler() server.ToolHandlerFunc { return CreateCountToolHandler( resources.ResourceTypeAccessControlPolicy, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Use ListAll function to get all resources resp, err := client.V3().ListAllAccessControlPolicy(ctx, "") if err != nil { return nil, err } res := map[string]interface{}{ "resource_type": "AccessControlPolicy", "count": len(resp.Entities), "metadata": resp.Metadata, } return res, nil }, ) } ``` -------------------------------------------------------------------------------- /pkg/tools/networksecurityrule.go: -------------------------------------------------------------------------------- ```go package tools import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/thunderboltsid/mcp-nutanix/pkg/resources" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // NetworkSecurityRule defines the NetworkSecurityRule tool func NetworkSecurityRuleList() mcp.Tool { return mcp.NewTool("networksecurityrule_list", mcp.WithDescription("List networksecurityrule resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // NetworkSecurityRuleListHandler implements the handler for the NetworkSecurityRule list tool func NetworkSecurityRuleListHandler() server.ToolHandlerFunc { return CreateListToolHandler( resources.ResourceTypeNetworkSecurityRule, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Use ListAll function to get all resources return client.V3().ListAllNetworkSecurityRule(ctx, "") }, ) } // NetworkSecurityRuleCount defines the NetworkSecurityRule count tool func NetworkSecurityRuleCount() mcp.Tool { return mcp.NewTool("networksecurityrule_count", mcp.WithDescription("Count networksecurityrule resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // NetworkSecurityRuleCountHandler implements the handler for the NetworkSecurityRule count tool func NetworkSecurityRuleCountHandler() server.ToolHandlerFunc { return CreateCountToolHandler( resources.ResourceTypeNetworkSecurityRule, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Use ListAll function to get all resources resp, err := client.V3().ListAllNetworkSecurityRule(ctx, "") if err != nil { return nil, err } res := map[string]interface{}{ "resource_type": "NetworkSecurityRule", "count": len(resp.Entities), "metadata": resp.Metadata, } return res, nil }, ) } ``` -------------------------------------------------------------------------------- /pkg/tools/recoveryplanjob.go: -------------------------------------------------------------------------------- ```go package tools import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/thunderboltsid/mcp-nutanix/pkg/resources" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" v3 "github.com/nutanix-cloud-native/prism-go-client/v3" ) // RecoveryPlanJob defines the RecoveryPlanJob tool func RecoveryPlanJobList() mcp.Tool { return mcp.NewTool("recoveryplanjob_list", mcp.WithDescription("List recoveryplanjob resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // RecoveryPlanJobListHandler implements the handler for the RecoveryPlanJob list tool func RecoveryPlanJobListHandler() server.ToolHandlerFunc { return CreateListToolHandler( resources.ResourceTypeRecoveryPlanJob, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Create DSMetadata without filter metadata := &v3.DSMetadata{} return client.V3().ListRecoveryPlanJobs(ctx, metadata) }, ) } // RecoveryPlanJobCount defines the RecoveryPlanJob count tool func RecoveryPlanJobCount() mcp.Tool { return mcp.NewTool("recoveryplanjob_count", mcp.WithDescription("Count recoveryplanjob resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // RecoveryPlanJobCountHandler implements the handler for the RecoveryPlanJob count tool func RecoveryPlanJobCountHandler() server.ToolHandlerFunc { return CreateCountToolHandler( resources.ResourceTypeRecoveryPlanJob, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { // Create DSMetadata without filter metadata := &v3.DSMetadata{} resp, err := client.V3().ListRecoveryPlanJobs(ctx, metadata) if err != nil { return nil, err } res := map[string]interface{}{ "resource_type": "RecoveryPlanJob", "count": len(resp.Entities), "metadata": resp.Metadata, } return res, nil }, ) } ``` -------------------------------------------------------------------------------- /pkg/prompts/credentials.go: -------------------------------------------------------------------------------- ```go package prompts import ( "context" "fmt" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) func SetCredentials() mcp.Prompt { return mcp.NewPrompt("credentials", mcp.WithPromptDescription("Credentials for Prism Central"), mcp.WithArgument("endpoint", mcp.RequiredArgument(), mcp.ArgumentDescription("Prism Central endpoint"), ), mcp.WithArgument("username", mcp.RequiredArgument(), mcp.ArgumentDescription("Username of the Prism Central user"), ), mcp.WithArgument("password", mcp.RequiredArgument(), mcp.ArgumentDescription("Password of the Prism Central user"), ), mcp.WithArgument("insecure", mcp.ArgumentDescription("Skip TLS verification (true/false)"), ), ) } func SetCredentialsResponse() server.PromptHandlerFunc { return func(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) { endpoint := request.Params.Arguments["endpoint"] username := request.Params.Arguments["username"] password := request.Params.Arguments["password"] insecure := request.Params.Arguments["insecure"] client.PrismClientProvider.UpdateValue("endpoint", endpoint) client.PrismClientProvider.UpdateValue("username", username) client.PrismClientProvider.UpdateValue("password", password) client.PrismClientProvider.UpdateValue("insecure", insecure) client.Init(client.PrismClientProvider) // Validate the credentials pcInfo, err := client.GetPrismClient().V3().GetPrismCentral(ctx) if err != nil { return mcp.NewGetPromptResult( "Failed to connect to Prism Central", []mcp.PromptMessage{ mcp.NewPromptMessage( mcp.RoleAssistant, mcp.NewTextContent(fmt.Sprintf("Failed to connect to Prism Central: %s. Please check your credentials and try again.", err.Error())), ), }, ), nil } return mcp.NewGetPromptResult( "Connected to Prism Central", []mcp.PromptMessage{ mcp.NewPromptMessage( mcp.RoleAssistant, 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)), ), }, ), nil } } ``` -------------------------------------------------------------------------------- /pkg/tools/common.go: -------------------------------------------------------------------------------- ```go package tools import ( "context" "fmt" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/thunderboltsid/mcp-nutanix/internal/json" "github.com/thunderboltsid/mcp-nutanix/pkg/resources" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // ListResourceFunc defines a function that handles listing a resource type type ListResourceFunc func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) // CreateListToolHandler creates a generic tool handler for listing resources func CreateListToolHandler( resourceType resources.ResourceType, listFunc ListResourceFunc, ) server.ToolHandlerFunc { return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Get the Prism client prismClient := client.GetPrismClient() if prismClient == nil { return nil, fmt.Errorf("prism client not initialized, please set credentials first") } // Get filter if provided (for LLM reference only) //filter, _ := request.Params.Arguments["filter"].(string) // List all resources resp, err := listFunc(ctx, prismClient, "") if err != nil { return nil, fmt.Errorf("failed to list %s: %w", resourceType, err) } // Convert to JSON cjson := json.CustomJSONEncoder(resp) jsonBytes, err := cjson.MarshalJSON() if err != nil { return nil, fmt.Errorf("failed to marshal %s: %w", resourceType, err) } return mcp.NewToolResultText(string(jsonBytes)), nil } } // CreateCountToolHandler creates a generic tool handler for counting resources func CreateCountToolHandler( resourceType resources.ResourceType, countFunc ListResourceFunc, ) server.ToolHandlerFunc { return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Get the Prism client prismClient := client.GetPrismClient() if prismClient == nil { return nil, fmt.Errorf("prism client not initialized, please set credentials first") } // Get filter if provided (for LLM reference only) //filter, _ := request.Params.Arguments["filter"].(string) // List all resources resp, err := countFunc(ctx, prismClient, "") if err != nil { return nil, fmt.Errorf("failed to list %s: %w", resourceType, err) } // Convert to JSON cjson := json.RegularJSONEncoder(resp) jsonBytes, err := cjson.MarshalJSON() if err != nil { return nil, fmt.Errorf("failed to marshal %s count: %w", resourceType, err) } return mcp.NewToolResultText(string(jsonBytes)), nil } } ``` -------------------------------------------------------------------------------- /internal/json/marshal.go: -------------------------------------------------------------------------------- ```go package json import ( "encoding/json" "fmt" "strings" "github.com/itchyny/gojq" ) // DefaultStripPaths is a list of default paths to strip from the JSON output. // These paths are used to remove unnecessary or sensitive information from the JSON response. // guest_customization on VMs is massive and could contain sensitive information. // spec and status are duplicative info and hence status can be removed. var DefaultStripPaths = []string{ "api_version", "spec.resources.guest_customization", "entities[].spec.resources.guest_customization", "entities[].status.resources.guest_customization", } func stripProperties(data []byte, paths []string) ([]byte, error) { var input interface{} if err := json.Unmarshal(data, &input); err != nil { return nil, err } // Start with identity query queryStr := "." for _, path := range paths { if strings.Contains(path, "[]") { // Handle array paths (entities[].status) parts := strings.Split(path, "[]") arrayPath := parts[0] fieldPath := parts[1] if len(fieldPath) > 0 && fieldPath[0] == '.' { fieldPath = fieldPath[1:] // Remove leading dot } // Correct jq syntax for modifying each element in an array queryStr += fmt.Sprintf(" | .%s |= map(del(.%s))", arrayPath, fieldPath) } else { // Simple path (api_version) queryStr += fmt.Sprintf(" | del(.%s)", path) } } // For debugging // fmt.Printf("JQ Query: %s\n", queryStr) query, err := gojq.Parse(queryStr) if err != nil { return nil, fmt.Errorf("jq parse error: %v for query: %s", err, queryStr) } code, err := gojq.Compile(query) if err != nil { return nil, fmt.Errorf("jq compile error: %v", err) } iter := code.Run(input) result, ok := iter.Next() if !ok { return nil, fmt.Errorf("jq query returned no results") } if err, ok := result.(error); ok { return nil, fmt.Errorf("jq execution error: %v", err) } return json.Marshal(result) } type CustomJSON struct { Value interface{} StripPaths []string } type RegularJSON struct { Value interface{} } func CustomJSONEncoder(value any) *CustomJSON { return &CustomJSON{ Value: value, StripPaths: DefaultStripPaths, } } func RegularJSONEncoder(value any) *RegularJSON { return &RegularJSON{ Value: value, } } func (r *RegularJSON) MarshalJSON() ([]byte, error) { data, err := json.Marshal(r.Value) if err != nil { return nil, err } return data, nil } func (d *CustomJSON) MarshalJSON() ([]byte, error) { data, err := json.Marshal(d.Value) if err != nil { return nil, err } return stripProperties(data, d.StripPaths) } ``` -------------------------------------------------------------------------------- /pkg/resources/common.go: -------------------------------------------------------------------------------- ```go package resources import ( "context" "fmt" "strings" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/thunderboltsid/mcp-nutanix/internal/json" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // ResourceType enum for different resource types type ResourceType string const ( ResourceTypeVM ResourceType = "vm" ResourceTypeSubnet ResourceType = "subnet" ResourceTypeImage ResourceType = "image" ResourceTypeCluster ResourceType = "cluster" ResourceTypeHost ResourceType = "host" ResourceTypeProject ResourceType = "project" ResourceTypeVolumeGroup ResourceType = "volumegroup" ResourceTypeNetworkSecurityRule ResourceType = "networksecurityrule" ResourceTypeServiceGroup ResourceType = "servicegroup" ResourceTypeAddressGroup ResourceType = "addressgroup" ResourceTypeAccessControlPolicy ResourceType = "accesscontrolpolicy" ResourceTypeRole ResourceType = "role" ResourceTypeUser ResourceType = "user" ResourceTypeUserGroup ResourceType = "usergroup" ResourceTypePermission ResourceType = "permission" ResourceTypeProtectionRule ResourceType = "protectionrule" ResourceTypeRecoveryPlan ResourceType = "recoveryplan" ResourceTypeRecoveryPlanJob ResourceType = "recoveryplanjob" ResourceTypeCategory ResourceType = "category" ResourceTypeCategoryValue ResourceType = "categoryvalue" ResourceTypeAvailabilityZone ResourceType = "availabilityzone" ) // ResourceHandlerFunc defines a function that handles a specific resource get operation type ResourceHandlerFunc func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) // ResourceURIPrefix returns the URI prefix for a resource type func ResourceURIPrefix(resourceType ResourceType) string { return fmt.Sprintf("%s://", resourceType) } // NutanixURI returns a URI for a resource type and UUID func NutanixURI(resourceType ResourceType, uuid string) string { return fmt.Sprintf("%s://%s", resourceType, uuid) } // ExtractIDFromURI extracts the UUID from a URI // uri is expected to be in the format of resourceType://uuid func ExtractIDFromURI(uri string) string { parts := strings.Split(uri, "://") if len(parts) != 2 { return "" } return parts[1] } // ExtractTypeFromURI extracts the resource type from a URI // uri is expected to be in the format of resourceType://uuid func ExtractTypeFromURI(uri string) ResourceType { parts := strings.Split(uri, "://") if len(parts) != 2 { return "" } return ResourceType(parts[0]) } // CreateResourceHandler creates a generic resource handler for any Nutanix resource func CreateResourceHandler(resourceType ResourceType, handlerFunc ResourceHandlerFunc) server.ResourceTemplateHandlerFunc { return func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) { uuid := ExtractIDFromURI(request.Params.URI) if uuid == "" { return nil, fmt.Errorf("URI must contain a UUID") } // Get the Prism client prismClient := client.GetPrismClient() if prismClient == nil { return nil, fmt.Errorf("prism client not initialized, please set credentials first") } // Call the specific resource handler resource, err := handlerFunc(ctx, prismClient, uuid) if err != nil { return nil, fmt.Errorf("failed to get %s: %w", resourceType, err) } // Convert to JSON cjson := json.CustomJSONEncoder(resource) jsonBytes, err := cjson.MarshalJSON() if err != nil { return nil, fmt.Errorf("failed to marshal %s details: %w", resourceType, err) } return []mcp.ResourceContents{ &mcp.TextResourceContents{ URI: request.Params.URI, MIMEType: "application/json", Text: string(jsonBytes), }, }, nil } } ``` -------------------------------------------------------------------------------- /internal/codegen/templates/tools.go: -------------------------------------------------------------------------------- ```go package templates import ( "fmt" "os" "strings" "text/template" ) // Templates for tool implementations const toolTemplate = `package tools import ( "context" "fmt" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/thunderboltsid/mcp-nutanix/pkg/resources" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" "github.com/nutanix-cloud-native/prism-go-client/v3" ) // {{.Name}} defines the {{.Name}} tool func {{.Name}}List() mcp.Tool { return mcp.NewTool("{{.ResourceType}}_list", mcp.WithDescription("List {{.ResourceType}} resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // {{.Name}}ListHandler implements the handler for the {{.Name}} list tool func {{.Name}}ListHandler() server.ToolHandlerFunc { return CreateListToolHandler( resources.ResourceType{{.Name}}, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { {{if eq .Name "Host"}} // Special case for Host which doesn't take a filter return client.V3().{{.ClientListAllFunc}}(ctx) {{else if eq .Name "Subnet"}} // Special case for Subnet which has an extra parameter return client.V3().{{.ClientListAllFunc}}(ctx, "", nil) {{else if eq .Name "Category"}} // Special case for Category which takes CategoryListMetadata metadata := &v3.CategoryListMetadata{} return client.V3().{{.ClientListFunc}}(ctx, metadata) {{else if .HasListAllFunc}} // Use ListAll function to get all resources return client.V3().{{.ClientListAllFunc}}(ctx, "") {{else}} // Create DSMetadata without filter metadata := &v3.DSMetadata{} return client.V3().{{.ClientListFunc}}(ctx, metadata) {{end}} }, ) } // {{.Name}}Count defines the {{.Name}} count tool func {{.Name}}Count() mcp.Tool { return mcp.NewTool("{{.ResourceType}}_count", mcp.WithDescription("Count {{.ResourceType}} resources"), mcp.WithString("filter", mcp.Description("Optional text filter (interpreted by LLM)"), ), ) } // {{.Name}}CountHandler implements the handler for the {{.Name}} count tool func {{.Name}}CountHandler() server.ToolHandlerFunc { return CreateCountToolHandler( resources.ResourceType{{.Name}}, // Define the ListResourceFunc implementation func(ctx context.Context, client *client.NutanixClient, filter string) (interface{}, error) { {{if eq .Name "Host"}} // Special case for Host which doesn't take a filter resp, err := client.V3().{{.ClientListAllFunc}}(ctx) {{else if eq .Name "Subnet"}} // Special case for Subnet which has an extra parameter resp, err := client.V3().{{.ClientListAllFunc}}(ctx, "", nil) {{else if eq .Name "Category"}} // Special case for Category which takes CategoryListMetadata metadata := &v3.CategoryListMetadata{} resp, err := client.V3().{{.ClientListFunc}}(ctx, metadata) {{else if .HasListAllFunc}} // Use ListAll function to get all resources resp, err := client.V3().{{.ClientListAllFunc}}(ctx, "") {{else}} // Create DSMetadata without filter metadata := &v3.DSMetadata{} resp, err := client.V3().{{.ClientListFunc}}(ctx, metadata) {{end}} if err != nil { return nil, err } res := map[string]interface{}{ "resource_type": "{{.Name}}", "count": len(resp.Entities), "metadata": resp.Metadata, } return res, nil }, ) } ` // GenerateToolFiles generates tool files for all Nutanix resources that support listing func GenerateToolFiles(baseDir string) error { resources := GetResourceDefinitions() // Create the tools directory if it doesn't exist toolsDir := fmt.Sprintf("%s/pkg/tools", baseDir) err := os.MkdirAll(toolsDir, 0755) if err != nil { return fmt.Errorf("error creating tools directory: %w", err) } // Parse the tool template toolTmpl, err := template.New("tool").Parse(toolTemplate) if err != nil { return fmt.Errorf("error parsing tool template: %w", err) } // Generate tool files for _, res := range resources { // Skip resources that don't support listing if !res.HasListFunc && !res.HasListAllFunc { fmt.Printf("Skipping tool generation for %s: no list capability\n", res.Name) continue } // Create tool file toolFilePath := fmt.Sprintf("%s/%s.go", toolsDir, strings.ToLower(res.Name)) toolFile, err := os.Create(toolFilePath) if err != nil { fmt.Printf("Error creating tool file for %s: %v\n", res.Name, err) continue } defer toolFile.Close() // Execute the template err = toolTmpl.Execute(toolFile, res) if err != nil { fmt.Printf("Error executing tool template for %s: %v\n", res.Name, err) } } return nil } ``` -------------------------------------------------------------------------------- /internal/codegen/templates/resources.go: -------------------------------------------------------------------------------- ```go package templates import ( "fmt" "os" "strings" "text/template" ) // Resource defines the structure for a Nutanix resource type Resource struct { Name string ResourceType string Description string ClientGetFunc string ClientListFunc string // Regular List function with DSMetadata parameter ClientListAllFunc string // ListAll function with filter string parameter HasListFunc bool // Whether the service has a ListX function HasListAllFunc bool // Whether the service has a ListAllX function } const resourceTemplate = `package resources import ( "context" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // {{.Name}} defines the {{.Name}} resource template func {{.Name}}() mcp.ResourceTemplate { return mcp.NewResourceTemplate( string(ResourceURIPrefix(ResourceType{{.Name}})) + "{uuid}", string(ResourceType{{.Name}}), mcp.WithTemplateDescription("{{.Description}}"), mcp.WithTemplateMIMEType("application/json"), ) } // {{.Name}}Handler implements the handler for the {{.Name}} resource func {{.Name}}Handler() server.ResourceTemplateHandlerFunc { return CreateResourceHandler(ResourceType{{.Name}}, func(ctx context.Context, client *client.NutanixClient, uuid string) (interface{}, error) { // Get the {{.Name}} return client.V3().{{.ClientGetFunc}}(ctx, uuid) }) } ` // GetResourceDefinitions returns all Nutanix resource definitions func GetResourceDefinitions() []Resource { return []Resource{ { Name: "VM", ResourceType: "vm", Description: "Virtual Machine resource", ClientGetFunc: "GetVM", ClientListFunc: "ListVM", ClientListAllFunc: "ListAllVM", HasListFunc: true, HasListAllFunc: true, }, { Name: "Cluster", ResourceType: "cluster", Description: "Cluster resource", ClientGetFunc: "GetCluster", ClientListFunc: "ListCluster", ClientListAllFunc: "ListAllCluster", HasListFunc: true, HasListAllFunc: true, }, { Name: "Image", ResourceType: "image", Description: "Image resource", ClientGetFunc: "GetImage", ClientListFunc: "ListImage", ClientListAllFunc: "ListAllImage", HasListFunc: true, HasListAllFunc: true, }, { Name: "Subnet", ResourceType: "subnet", Description: "Subnet resource", ClientGetFunc: "GetSubnet", ClientListFunc: "ListSubnet", ClientListAllFunc: "ListAllSubnet", HasListFunc: true, HasListAllFunc: true, }, { Name: "Host", ResourceType: "host", Description: "Host resource", ClientGetFunc: "GetHost", ClientListFunc: "ListHost", ClientListAllFunc: "ListAllHost", HasListFunc: true, HasListAllFunc: true, }, { Name: "Project", ResourceType: "project", Description: "Project resource", ClientGetFunc: "GetProject", ClientListFunc: "ListProject", ClientListAllFunc: "ListAllProject", HasListFunc: true, HasListAllFunc: true, }, { Name: "VolumeGroup", ResourceType: "volumegroup", Description: "Volume Group resource", ClientGetFunc: "GetVolumeGroup", ClientListFunc: "ListVolumeGroup", ClientListAllFunc: "", HasListFunc: true, HasListAllFunc: false, }, { Name: "NetworkSecurityRule", ResourceType: "networksecurityrule", Description: "Network Security Rule resource", ClientGetFunc: "GetNetworkSecurityRule", ClientListFunc: "ListNetworkSecurityRule", ClientListAllFunc: "ListAllNetworkSecurityRule", HasListFunc: true, HasListAllFunc: true, }, { Name: "Category", ResourceType: "category", Description: "Category resource", ClientGetFunc: "GetCategoryKey", ClientListFunc: "ListCategories", ClientListAllFunc: "", HasListFunc: true, HasListAllFunc: false, }, { Name: "AccessControlPolicy", ResourceType: "accesscontrolpolicy", Description: "Access Control Policy resource", ClientGetFunc: "GetAccessControlPolicy", ClientListFunc: "ListAccessControlPolicy", ClientListAllFunc: "ListAllAccessControlPolicy", HasListFunc: true, HasListAllFunc: true, }, { Name: "Role", ResourceType: "role", Description: "Role resource", ClientGetFunc: "GetRole", ClientListFunc: "ListRole", ClientListAllFunc: "ListAllRole", HasListFunc: true, HasListAllFunc: true, }, { Name: "User", ResourceType: "user", Description: "User resource", ClientGetFunc: "GetUser", ClientListFunc: "ListUser", ClientListAllFunc: "ListAllUser", HasListFunc: true, HasListAllFunc: true, }, { Name: "UserGroup", ResourceType: "usergroup", Description: "User Group resource", ClientGetFunc: "GetUserGroup", ClientListFunc: "ListUserGroup", ClientListAllFunc: "ListAllUserGroup", HasListFunc: true, HasListAllFunc: true, }, { Name: "Permission", ResourceType: "permission", Description: "Permission resource", ClientGetFunc: "GetPermission", ClientListFunc: "ListPermission", ClientListAllFunc: "ListAllPermission", HasListFunc: true, HasListAllFunc: true, }, { Name: "ProtectionRule", ResourceType: "protectionrule", Description: "Protection Rule resource", ClientGetFunc: "GetProtectionRule", ClientListFunc: "ListProtectionRules", ClientListAllFunc: "ListAllProtectionRules", HasListFunc: true, HasListAllFunc: true, }, { Name: "RecoveryPlan", ResourceType: "recoveryplan", Description: "Recovery Plan resource", ClientGetFunc: "GetRecoveryPlan", ClientListFunc: "ListRecoveryPlans", ClientListAllFunc: "ListAllRecoveryPlans", HasListFunc: true, HasListAllFunc: true, }, { Name: "ServiceGroup", ResourceType: "servicegroup", Description: "Service Group resource", ClientGetFunc: "GetServiceGroup", ClientListFunc: "", ClientListAllFunc: "ListAllServiceGroups", HasListFunc: false, HasListAllFunc: true, }, { Name: "AddressGroup", ResourceType: "addressgroup", Description: "Address Group resource", ClientGetFunc: "GetAddressGroup", ClientListFunc: "ListAddressGroups", ClientListAllFunc: "ListAllAddressGroups", HasListFunc: true, HasListAllFunc: true, }, { Name: "RecoveryPlanJob", ResourceType: "recoveryplanjob", Description: "Recovery Plan Job resource", ClientGetFunc: "GetRecoveryPlanJob", ClientListFunc: "ListRecoveryPlanJobs", ClientListAllFunc: "", HasListFunc: true, HasListAllFunc: false, }, { Name: "AvailabilityZone", ResourceType: "availabilityzone", Description: "Availability Zone resource", ClientGetFunc: "GetAvailabilityZone", ClientListFunc: "", ClientListAllFunc: "", HasListFunc: false, HasListAllFunc: false, }, } } // GenerateResourceFiles generates resource files for all Nutanix resources func GenerateResourceFiles(baseDir string) error { resources := GetResourceDefinitions() // Create the resources directory if it doesn't exist resourcesDir := fmt.Sprintf("%s/pkg/resources", baseDir) err := os.MkdirAll(resourcesDir, 0755) if err != nil { return fmt.Errorf("error creating resources directory: %w", err) } // Parse the resource template tmpl, err := template.New("resource").Parse(resourceTemplate) if err != nil { return fmt.Errorf("error parsing resource template: %w", err) } // Generate resource files for _, res := range resources { // Create resource file resourceFilePath := fmt.Sprintf("%s/%s.go", resourcesDir, strings.ToLower(res.Name)) resourceFile, err := os.Create(resourceFilePath) if err != nil { fmt.Printf("Error creating resource file for %s: %v\n", res.Name, err) continue } defer resourceFile.Close() // Execute the template err = tmpl.Execute(resourceFile, res) if err != nil { fmt.Printf("Error executing resource template for %s: %v\n", res.Name, err) } } return nil } ``` -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- ```go package main import ( "fmt" "os" "github.com/thunderboltsid/mcp-nutanix/internal/client" "github.com/thunderboltsid/mcp-nutanix/pkg/prompts" "github.com/thunderboltsid/mcp-nutanix/pkg/resources" "github.com/thunderboltsid/mcp-nutanix/pkg/tools" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // ToolRegistration holds a tool function and its handler type ToolRegistration struct { Func func() mcp.Tool Handler server.ToolHandlerFunc } // ResourceRegistration represents a resource and its associated tools type ResourceRegistration struct { Tools []ToolRegistration ResourceFunc func() mcp.ResourceTemplate ResourceHandler server.ResourceTemplateHandlerFunc } // initializeFromEnvIfAvailable initializes the Prism client only if environment variables are available func initializeFromEnvIfAvailable() { endpoint := os.Getenv("NUTANIX_ENDPOINT") username := os.Getenv("NUTANIX_USERNAME") password := os.Getenv("NUTANIX_PASSWORD") // Only initialize if all required environment variables are set // This allows prompt-based initialization to work when env vars are not present if endpoint != "" && username != "" && password != "" { client.Init(client.PrismClientProvider) fmt.Printf("Initialized Prism client from environment variables for endpoint: %s\n", endpoint) } } func main() { // Initialize the Prism client only if environment variables are available initializeFromEnvIfAvailable() // Define server hooks for logging and debugging hooks := &server.Hooks{} hooks.AddOnError(func(id any, method mcp.MCPMethod, message any, err error) { fmt.Printf("onError: %s, %v, %v, %v\n", method, id, message, err) }) // Log level based on environment variable debugMode := os.Getenv("DEBUG") != "" if debugMode { hooks.AddBeforeAny(func(id any, method mcp.MCPMethod, message any) { fmt.Printf("beforeAny: %s, %v, %v\n", method, id, message) }) hooks.AddOnSuccess(func(id any, method mcp.MCPMethod, message any, result any) { fmt.Printf("onSuccess: %s, %v, %v, %v\n", method, id, message, result) }) hooks.AddBeforeInitialize(func(id any, message *mcp.InitializeRequest) { fmt.Printf("beforeInitialize: %v, %v\n", id, message) }) hooks.AddAfterInitialize(func(id any, message *mcp.InitializeRequest, result *mcp.InitializeResult) { fmt.Printf("afterInitialize: %v, %v, %v\n", id, message, result) }) hooks.AddAfterCallTool(func(id any, message *mcp.CallToolRequest, result *mcp.CallToolResult) { fmt.Printf("afterCallTool: %v, %v, %v\n", id, message, result) }) hooks.AddBeforeCallTool(func(id any, message *mcp.CallToolRequest) { fmt.Printf("beforeCallTool: %v, %v\n", id, message) }) } // Create a new MCP server s := server.NewMCPServer( "Prism Central", "0.0.1", server.WithResourceCapabilities(true, true), server.WithPromptCapabilities(true), server.WithLogging(), server.WithHooks(hooks), ) // Add the prompts s.AddPrompt(prompts.SetCredentials(), prompts.SetCredentialsResponse()) // Add standalone tools s.AddTool(tools.ApiNamespacesList(), tools.ApiNamespacesListHandler()) // Define all resources and tools resourceRegistrations := map[string]ResourceRegistration{ "vm": { Tools: []ToolRegistration{ { Func: tools.VMList, Handler: tools.VMListHandler(), }, { Func: tools.VMCount, Handler: tools.VMCountHandler(), }, }, ResourceFunc: resources.VM, ResourceHandler: resources.VMHandler(), }, "cluster": { Tools: []ToolRegistration{ { Func: tools.ClusterList, Handler: tools.ClusterListHandler(), }, { Func: tools.ClusterCount, Handler: tools.ClusterCountHandler(), }, }, ResourceFunc: resources.Cluster, ResourceHandler: resources.ClusterHandler(), }, "host": { Tools: []ToolRegistration{ { Func: tools.HostList, Handler: tools.HostListHandler(), }, { Func: tools.HostCount, Handler: tools.HostCountHandler(), }, }, ResourceFunc: resources.Host, ResourceHandler: resources.HostHandler(), }, "image": { Tools: []ToolRegistration{ { Func: tools.ImageList, Handler: tools.ImageListHandler(), }, { Func: tools.ImageCount, Handler: tools.ImageCountHandler(), }, }, ResourceFunc: resources.Image, ResourceHandler: resources.ImageHandler(), }, "subnet": { Tools: []ToolRegistration{ { Func: tools.SubnetList, Handler: tools.SubnetListHandler(), }, { Func: tools.SubnetCount, Handler: tools.SubnetCountHandler(), }, }, ResourceFunc: resources.Subnet, ResourceHandler: resources.SubnetHandler(), }, "project": { Tools: []ToolRegistration{ { Func: tools.ProjectList, Handler: tools.ProjectListHandler(), }, { Func: tools.ProjectCount, Handler: tools.ProjectCountHandler(), }, }, ResourceFunc: resources.Project, ResourceHandler: resources.ProjectHandler(), }, "volumegroup": { Tools: []ToolRegistration{ { Func: tools.VolumeGroupList, Handler: tools.VolumeGroupListHandler(), }, { Func: tools.VolumeGroupCount, Handler: tools.VolumeGroupCountHandler(), }, }, ResourceFunc: resources.VolumeGroup, ResourceHandler: resources.VolumeGroupHandler(), }, "networksecurityrule": { Tools: []ToolRegistration{ { Func: tools.NetworkSecurityRuleList, Handler: tools.NetworkSecurityRuleListHandler(), }, { Func: tools.NetworkSecurityRuleCount, Handler: tools.NetworkSecurityRuleCountHandler(), }, }, ResourceFunc: resources.NetworkSecurityRule, ResourceHandler: resources.NetworkSecurityRuleHandler(), }, "category": { Tools: []ToolRegistration{ { Func: tools.CategoryList, Handler: tools.CategoryListHandler(), }, { Func: tools.CategoryCount, Handler: tools.CategoryCountHandler(), }, }, ResourceFunc: resources.Category, ResourceHandler: resources.CategoryHandler(), }, "accesscontrolpolicy": { Tools: []ToolRegistration{ { Func: tools.AccessControlPolicyList, Handler: tools.AccessControlPolicyListHandler(), }, { Func: tools.AccessControlPolicyCount, Handler: tools.AccessControlPolicyCountHandler(), }, }, ResourceFunc: resources.AccessControlPolicy, ResourceHandler: resources.AccessControlPolicyHandler(), }, "role": { Tools: []ToolRegistration{ { Func: tools.RoleList, Handler: tools.RoleListHandler(), }, { Func: tools.RoleCount, Handler: tools.RoleCountHandler(), }, }, ResourceFunc: resources.Role, ResourceHandler: resources.RoleHandler(), }, "user": { Tools: []ToolRegistration{ { Func: tools.UserList, Handler: tools.UserListHandler(), }, { Func: tools.UserCount, Handler: tools.UserCountHandler(), }, }, ResourceFunc: resources.User, ResourceHandler: resources.UserHandler(), }, "usergroup": { Tools: []ToolRegistration{ { Func: tools.UserGroupList, Handler: tools.UserGroupListHandler(), }, { Func: tools.UserGroupCount, Handler: tools.UserGroupCountHandler(), }, }, ResourceFunc: resources.UserGroup, ResourceHandler: resources.UserGroupHandler(), }, "permission": { Tools: []ToolRegistration{ { Func: tools.PermissionList, Handler: tools.PermissionListHandler(), }, { Func: tools.PermissionCount, Handler: tools.PermissionCountHandler(), }, }, ResourceFunc: resources.Permission, ResourceHandler: resources.PermissionHandler(), }, "protectionrule": { Tools: []ToolRegistration{ { Func: tools.ProtectionRuleList, Handler: tools.ProtectionRuleListHandler(), }, { Func: tools.ProtectionRuleCount, Handler: tools.ProtectionRuleCountHandler(), }, }, ResourceFunc: resources.ProtectionRule, ResourceHandler: resources.ProtectionRuleHandler(), }, "recoveryplan": { Tools: []ToolRegistration{ { Func: tools.RecoveryPlanList, Handler: tools.RecoveryPlanListHandler(), }, { Func: tools.RecoveryPlanCount, Handler: tools.RecoveryPlanCountHandler(), }, }, ResourceFunc: resources.RecoveryPlan, ResourceHandler: resources.RecoveryPlanHandler(), }, "servicegroup": { Tools: []ToolRegistration{ { Func: tools.ServiceGroupList, Handler: tools.ServiceGroupListHandler(), }, { Func: tools.ServiceGroupCount, Handler: tools.ServiceGroupCountHandler(), }, }, ResourceFunc: resources.ServiceGroup, ResourceHandler: resources.ServiceGroupHandler(), }, "addressgroup": { Tools: []ToolRegistration{ { Func: tools.AddressGroupList, Handler: tools.AddressGroupListHandler(), }, { Func: tools.AddressGroupCount, Handler: tools.AddressGroupCountHandler(), }, }, ResourceFunc: resources.AddressGroup, ResourceHandler: resources.AddressGroupHandler(), }, "recoveryplanjob": { Tools: []ToolRegistration{ { Func: tools.RecoveryPlanJobList, Handler: tools.RecoveryPlanJobListHandler(), }, { Func: tools.RecoveryPlanJobCount, Handler: tools.RecoveryPlanJobCountHandler(), }, }, ResourceFunc: resources.RecoveryPlanJob, ResourceHandler: resources.RecoveryPlanJobHandler(), }, } // Register all tools and resources for name, registration := range resourceRegistrations { // Add all tools for _, tool := range registration.Tools { s.AddTool(tool.Func(), tool.Handler) if debugMode { fmt.Printf("Registered %s resource and tool\n", name) } } // Add the resource s.AddResourceTemplate(registration.ResourceFunc(), registration.ResourceHandler) } // Start the server if err := server.ServeStdio(s); err != nil { fmt.Printf("Server error: %v\n", err) } } ```