#
tokens: 26746/50000 13/13 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .github
│   └── workflows
│       └── build.yml
├── .gitignore
├── cmd
│   └── harvester-mcp-server
│       └── main.go
├── Dockerfile
├── go.mod
├── go.sum
├── LICENSE
├── Makefile
├── pkg
│   ├── client
│   │   └── client.go
│   ├── cmd
│   │   └── root.go
│   ├── kubernetes
│   │   ├── core_formatters.go
│   │   ├── formatters.go
│   │   ├── harvester_formatters.go
│   │   ├── resources.go
│   │   └── types.go
│   └── mcp
│       └── server.go
└── README.md
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
# vendor/

# Go workspace file
go.work
go.work.sum

# env file
.env

# IDE files
.idea/
.vscode/
*.swp
*.swo

# OS specific files
.DS_Store
Thumbs.db

# Build artifacts
bin/
harvester-mcp-server

```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# Harvester MCP Server

Model Context Protocol (MCP) server for Harvester HCI that enables Claude Desktop, Cursor, and other AI assistants to interact with Harvester clusters through the MCP protocol.

## Overview

Harvester MCP Server is a Go implementation of the [Model Context Protocol (MCP)](https://spec.modelcontextprotocol.io/specification/2024-11-05/) specifically designed for [Harvester HCI](https://github.com/harvester/harvester). It allows AI assistants like Claude Desktop and Cursor to perform CRUD operations on Harvester clusters, which are essentially Kubernetes clusters with Harvester-specific CRDs.

## Workflow

The following diagram illustrates how Harvester MCP Server bridges the gap between AI assistants and Harvester clusters:

```mermaid
graph LR;
    subgraph "AI Assistants"
        A[Claude Desktop] --> C[MCP Client];
        B[Cursor IDE] --> C;
    end
    
    subgraph "Harvester MCP Server"
        C --> D[MCP Server];
        D --> E[Resource Handler];
        E --> F[Formatter Registry];
        F -->|Get Formatter| G[Core Resource Formatters];
        F -->|Get Formatter| H[Harvester Resource Formatters];
    end
    
    subgraph "Kubernetes / Harvester"
        G --> I[Kubernetes API];
        H --> I;
        I --> J[Harvester Cluster];
    end
    
    style A fill:#f9f,stroke:#333,stroke-width:2px;
    style B fill:#f9f,stroke:#333,stroke-width:2px;
    style D fill:#bbf,stroke:#333,stroke-width:2px;
    style J fill:#bfb,stroke:#333,stroke-width:2px;
```

### How It Works

1. **LLM Integration**: AI assistants like Claude Desktop and Cursor connect to Harvester MCP Server via the MCP protocol.
2. **Request Processing**: The MCP Server receives natural language requests from the AI assistants and translates them into specific Kubernetes operations.
3. **Resource Handling**: The Resource Handler identifies the resource type and operation being requested.
4. **Formatter Selection**: The Formatter Registry selects the appropriate formatter for the resource type.
5. **API Interaction**: The server interacts with the Kubernetes API of the Harvester cluster.
6. **Response Formatting**: Results are formatted into human-readable text optimized for LLM consumption.
7. **User Presentation**: Formatted responses are returned to the AI assistant to present to the user.

This architecture enables AI assistants to interact with Harvester clusters through natural language, making complex Kubernetes operations more accessible to users.

## Features

- **Kubernetes Core Resources**:

  - Pods: List, Get, Delete
  - Deployments: List, Get
  - Services: List, Get
  - Namespaces: List, Get
  - Nodes: List, Get
  - Custom Resource Definitions (CRDs): List

- **Harvester-Specific Resources**:

  - Virtual Machines: List, Get
  - Images: List
  - Volumes: List
  - Networks: List

- **Enhanced User Experience**:
  - Human-readable formatted outputs for all resources
  - Automatic grouping of resources by namespace or status
  - Concise summaries with the most relevant information
  - Detailed views for comprehensive resource inspection

## Requirements

- Go 1.23+
- Access to a Harvester cluster with a valid kubeconfig

## Installation

### From Source

```bash
# Clone the repository
git clone https://github.com/starbops/harvester-mcp-server.git
cd harvester-mcp-server

# Build
make build

# Run
./bin/harvester-mcp-server
```

### Using Go Install

```bash
go install github.com/starbops/harvester-mcp-server/cmd/harvester-mcp-server@latest
```

## Configuration

The server automatically looks for Kubernetes configuration in the following order:

1. In-cluster configuration (if running inside a Kubernetes cluster)
2. Path specified by the `--kubeconfig` flag
3. Path specified by the `KUBECONFIG` environment variable
4. Default location at `~/.kube/config`

### Command-Line Flags

```
Usage:
  harvester-mcp-server [flags]

Flags:
  -h, --help                help for harvester-mcp-server
      --kubeconfig string   Path to the kubeconfig file (default is $KUBECONFIG or $HOME/.kube/config)
      --log-level string    Log level (debug, info, warn, error, fatal, panic) (default "info")
```

### Examples

Using a specific kubeconfig file:

```bash
harvester-mcp-server --kubeconfig=/path/to/kubeconfig.yaml
```

Using the KUBECONFIG environment variable:

```bash
export KUBECONFIG=$HOME/config.yaml
harvester-mcp-server
```

With debug logging:
```bash
harvester-mcp-server --log-level=debug
```

## Usage with Claude Desktop

1. Install Claude Desktop
2. Open Claude Desktop configuration file (`~/Library/Application\ Support/Claude/claude_desktop_config.json` or similar)
3. Add the Harvester MCP server to the `mcpServers` section:

```json
{
  "mcpServers": {
    "harvester": {
      "command": "/path/to/harvester-mcp-server",
      "args": ["--kubeconfig", "/path/to/kubeconfig.yaml", "--log-level", "info"]
    }
  }
}
```

4. Restart Claude Desktop
5. The Harvester MCP tools should now be available to Claude

## Example Queries for Claude Desktop

Once your Harvester MCP server is configured in Claude Desktop, you can ask questions like:

- "How many nodes are in my Harvester cluster?"
- "List all pods in the cattle-system namespace"
- "Show me the details of the pod named rancher-789c976c6-xbvmd in cattle-system namespace"
- "List all virtual machines in the default namespace"
- "What services are running in the harvester-system namespace?"

## Development

### Project Structure

- `cmd/harvester-mcp-server`: Main application entry point
- `pkg/client`: Kubernetes client implementation
- `pkg/cmd`: CLI commands implementation using Cobra
- `pkg/mcp`: MCP server implementation
- `pkg/kubernetes`: Unified resource handlers for Kubernetes resources
- `pkg/tools`: Legacy tool implementations for interacting with Harvester resources

### Resource Handling

The project uses a unified approach to handle Kubernetes resources:

1. The `pkg/kubernetes/resources.go` file contains a `ResourceHandler` that provides common operations for all resource types:
   - Listing resources with proper namespace handling
   - Getting resource details by name
   - Creating and updating resources
   - Deleting resources

2. The `pkg/kubernetes/formatters*.go` files contain formatters for different resource types:
   - Each formatter converts raw Kubernetes objects into human-readable text
   - Resource-specific details are extracted and formatted in a consistent way
   - Custom formatters exist for both standard Kubernetes resources and Harvester-specific resources

3. The `pkg/kubernetes/types.go` file maps friendly resource type names to Kubernetes GroupVersionResource objects:
   - Makes it easy to refer to resources by simple names in code
   - Centralizes resource type definitions

### Adding New Tools

To add a new tool:

1. If it's a new resource type, add it to `pkg/kubernetes/types.go`
2. Implement formatters for the resource in one of the formatter files
3. Register the tool in `pkg/mcp/server.go` in the `registerTools` method using the unified resource handler

### Formatting Functions

Each resource type has two formatting functions:
1. `formatXList` - Formats a list of resources with concise information, grouped by namespace
2. `formatX` - Formats a single resource with detailed information

## License

This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.

## Acknowledgments

- [Harvester HCI](https://github.com/harvester/harvester) - The foundation for this project
- [mark3labs/mcp-go](https://github.com/mark3labs/mcp-go) - The Go SDK for Model Context Protocol
- [manusa/kubernetes-mcp-server](https://github.com/manusa/kubernetes-mcp-server) - Reference implementation for Kubernetes MCP server
```

--------------------------------------------------------------------------------
/cmd/harvester-mcp-server/main.go:
--------------------------------------------------------------------------------

```go
package main

import (
	"github.com/starbops/harvester-mcp-server/pkg/cmd"
)

func main() {
	cmd.Execute()
}

```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
FROM docker.io/library/golang:1.23-alpine AS builder

WORKDIR /workspace

# Copy go.mod and go.sum
COPY go.mod go.sum ./

# Download dependencies
RUN go mod download

# Copy the source code
COPY . .

# Build the application
RUN go build -o harvester-mcp-server ./cmd/harvester-mcp-server

# Create a minimal runtime image
FROM alpine:latest

RUN apk --no-cache add ca-certificates

WORKDIR /app

# Copy the binary from the builder stage
COPY --from=builder /workspace/harvester-mcp-server /app/harvester-mcp-server

# Set the entry point
ENTRYPOINT ["/app/harvester-mcp-server"]
```

--------------------------------------------------------------------------------
/pkg/cmd/root.go:
--------------------------------------------------------------------------------

```go
package cmd

import (
	"fmt"

	log "github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	"github.com/starbops/harvester-mcp-server/pkg/mcp"
)

var (
	// Global flags
	kubeConfigPath string
	logLevel       string

	// Root command
	rootCmd = &cobra.Command{
		Use:   "harvester-mcp-server",
		Short: "Harvester MCP Server - MCP server for Harvester HCI",
		Long: `Harvester MCP Server is a Model Context Protocol (MCP) server for Harvester HCI.
It allows AI assistants like Claude and Cursor to interact with Harvester clusters through the MCP protocol.`,
		RunE: func(cmd *cobra.Command, args []string) error {
			// Configure log level
			level, err := log.ParseLevel(logLevel)
			if err != nil {
				log.Warnf("Invalid log level '%s', defaulting to 'info'", logLevel)
				level = log.InfoLevel
			}
			log.SetLevel(level)

			return runServer()
		},
		// Disable the automatic help message when an error occurs
		SilenceUsage: true,
		// Disable automatic error printing since we'll handle it explicitly
		SilenceErrors: true,
	}
)

// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
	if err := rootCmd.Execute(); err != nil {
		log.Fatalf("%v", err)
	}
}

func init() {
	// Configure default logger settings
	log.SetFormatter(&log.TextFormatter{
		FullTimestamp: true,
	})

	// Add flags
	rootCmd.PersistentFlags().StringVar(&kubeConfigPath, "kubeconfig", "", "Path to the kubeconfig file (default is $KUBECONFIG or $HOME/.kube/config)")
	rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "Log level (debug, info, warn, error, fatal, panic)")
}

func runServer() error {
	log.Info("Starting Harvester MCP Server")

	// Create server configuration
	cfg := &mcp.Config{
		KubeConfigPath: kubeConfigPath,
	}

	// Create and start the MCP server
	log.Debug("Creating MCP server instance")
	server, err := mcp.NewServer(cfg)
	if err != nil {
		return fmt.Errorf("failed to create MCP server: %w", err)
	}

	// Start the server
	log.Info("Starting MCP server (using stdio for communication)")
	if err := server.ServeStdio(); err != nil {
		return fmt.Errorf("failed to start MCP server: %w", err)
	}

	return nil
}

```

--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------

```yaml
name: Build and Release

on:
  push:
    branches: [ main ]
  # Enable manual trigger for testing purposes
  workflow_dispatch:

jobs:
  build:
    name: Build and Upload
    runs-on: ubuntu-latest
    permissions:
      contents: write # Required for uploading to GitHub releases
    
    steps:
      - name: Check out code
        uses: actions/checkout@v4
      
      - name: Set up Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.23'
          check-latest: true
          cache: true
      
      - name: Build for Linux (amd64)
        run: |
          mkdir -p bin
          GOOS=linux GOARCH=amd64 go build -o bin/harvester-mcp-server-linux-amd64 ./cmd/harvester-mcp-server
          chmod +x bin/harvester-mcp-server-linux-amd64
      
      - name: Build for Linux (arm64)
        run: |
          GOOS=linux GOARCH=arm64 go build -o bin/harvester-mcp-server-linux-arm64 ./cmd/harvester-mcp-server
          chmod +x bin/harvester-mcp-server-linux-arm64
      
      - name: Build for macOS (amd64)
        run: |
          GOOS=darwin GOARCH=amd64 go build -o bin/harvester-mcp-server-darwin-amd64 ./cmd/harvester-mcp-server
          chmod +x bin/harvester-mcp-server-darwin-amd64
      
      - name: Build for macOS (arm64)
        run: |
          GOOS=darwin GOARCH=arm64 go build -o bin/harvester-mcp-server-darwin-arm64 ./cmd/harvester-mcp-server
          chmod +x bin/harvester-mcp-server-darwin-arm64
      
      - name: Upload artifacts
        uses: actions/upload-artifact@v4
        with:
          name: harvester-mcp-server-binaries
          path: bin/
          retention-days: 7
      
      - name: Create release tag
        id: tag
        run: |
          TIMESTAMP=$(date +'%Y%m%d%H%M%S')
          echo "tag=release-${TIMESTAMP}" >> $GITHUB_OUTPUT
      
      - name: Create GitHub Release
        uses: softprops/action-gh-release@v1
        with:
          name: Automatic Build ${{ steps.tag.outputs.tag }}
          tag_name: ${{ steps.tag.outputs.tag }}
          files: |
            bin/harvester-mcp-server-linux-amd64
            bin/harvester-mcp-server-linux-arm64
            bin/harvester-mcp-server-darwin-amd64
            bin/harvester-mcp-server-darwin-arm64
          generate_release_notes: true
          prerelease: true
          fail_on_unmatched_files: true 
```

--------------------------------------------------------------------------------
/pkg/client/client.go:
--------------------------------------------------------------------------------

```go
package client

import (
	"fmt"
	"os"
	"path/filepath"

	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
)

// Config represents the configuration for the Kubernetes client.
type Config struct {
	// KubeConfigPath is the path to the kubeconfig file.
	// If empty, it defaults to the KUBECONFIG environment variable,
	// then to ~/.kube/config.
	KubeConfigPath string
}

// Client represents a Kubernetes client for interacting with Harvester clusters.
type Client struct {
	Clientset *kubernetes.Clientset
	Config    *rest.Config
}

// NewClient creates a new Kubernetes client.
func NewClient(cfg *Config) (*Client, error) {
	config, err := getKubeConfig(cfg.KubeConfigPath)
	if err != nil {
		return nil, fmt.Errorf("failed to get Kubernetes config: %w", err)
	}

	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		return nil, fmt.Errorf("failed to create Kubernetes clientset: %w", err)
	}

	return &Client{
		Clientset: clientset,
		Config:    config,
	}, nil
}

// getKubeConfig returns a Kubernetes configuration.
func getKubeConfig(kubeConfigPath string) (*rest.Config, error) {
	// Try to use in-cluster config if running in a Kubernetes cluster
	config, err := rest.InClusterConfig()
	if err == nil {
		return config, nil
	}

	// If kubeConfigPath is specified, use it
	if kubeConfigPath != "" {
		config, err := clientcmd.BuildConfigFromFlags("", kubeConfigPath)
		if err != nil {
			return nil, fmt.Errorf("failed to build config from specified kubeconfig file %s: %w", kubeConfigPath, err)
		}
		return config, nil
	}

	// Check KUBECONFIG environment variable
	envKubeconfig := os.Getenv("KUBECONFIG")
	if envKubeconfig != "" {
		config, err := clientcmd.BuildConfigFromFlags("", envKubeconfig)
		if err != nil {
			return nil, fmt.Errorf("failed to build config from KUBECONFIG environment variable %s: %w", envKubeconfig, err)
		}
		return config, nil
	}

	// Fall back to default kubeconfig location
	kubeconfig := filepath.Join(homeDir(), ".kube", "config")
	config, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
	if err != nil {
		return nil, fmt.Errorf("failed to build config from default kubeconfig file: %w", err)
	}

	return config, nil
}

// homeDir returns the user's home directory.
func homeDir() string {
	home, err := os.UserHomeDir()
	if err != nil {
		return ""
	}
	return home
}

```

--------------------------------------------------------------------------------
/pkg/kubernetes/types.go:
--------------------------------------------------------------------------------

```go
package kubernetes

import (
	"k8s.io/apimachinery/pkg/runtime/schema"
)

// Define constants for supported resource types
const (
	ResourceTypePod         = "pod"
	ResourceTypePods        = "pods"
	ResourceTypeDeployment  = "deployment"
	ResourceTypeDeployments = "deployments"
	ResourceTypeService     = "service"
	ResourceTypeServices    = "services"
	ResourceTypeNamespace   = "namespace"
	ResourceTypeNamespaces  = "namespaces"
	ResourceTypeNode        = "node"
	ResourceTypeNodes       = "nodes"
	ResourceTypeCRD         = "crd"
	ResourceTypeCRDs        = "crds"
	ResourceTypeVM          = "vm"
	ResourceTypeVMs         = "vms"
	ResourceTypeVolume      = "volume"
	ResourceTypeVolumes     = "volumes"
	ResourceTypeNetwork     = "network"
	ResourceTypeNetworks    = "networks"
	ResourceTypeImage       = "image"
	ResourceTypeImages      = "images"
)

// ResourceTypeToGVR maps friendly resource type names to GroupVersionResource
var ResourceTypeToGVR = map[string]schema.GroupVersionResource{
	// Core Kubernetes resources
	ResourceTypePod:         {Group: "", Version: "v1", Resource: "pods"},
	ResourceTypePods:        {Group: "", Version: "v1", Resource: "pods"},
	ResourceTypeService:     {Group: "", Version: "v1", Resource: "services"},
	ResourceTypeServices:    {Group: "", Version: "v1", Resource: "services"},
	ResourceTypeNamespace:   {Group: "", Version: "v1", Resource: "namespaces"},
	ResourceTypeNamespaces:  {Group: "", Version: "v1", Resource: "namespaces"},
	ResourceTypeNode:        {Group: "", Version: "v1", Resource: "nodes"},
	ResourceTypeNodes:       {Group: "", Version: "v1", Resource: "nodes"},
	ResourceTypeDeployment:  {Group: "apps", Version: "v1", Resource: "deployments"},
	ResourceTypeDeployments: {Group: "apps", Version: "v1", Resource: "deployments"},
	ResourceTypeCRD:         {Group: "apiextensions.k8s.io", Version: "v1", Resource: "customresourcedefinitions"},
	ResourceTypeCRDs:        {Group: "apiextensions.k8s.io", Version: "v1", Resource: "customresourcedefinitions"},

	// Harvester-specific resources
	ResourceTypeVM:       {Group: "kubevirt.io", Version: "v1", Resource: "virtualmachines"},
	ResourceTypeVMs:      {Group: "kubevirt.io", Version: "v1", Resource: "virtualmachines"},
	ResourceTypeVolume:   {Group: "storage.harvesterhci.io", Version: "v1beta1", Resource: "volumes"},
	ResourceTypeVolumes:  {Group: "storage.harvesterhci.io", Version: "v1beta1", Resource: "volumes"},
	ResourceTypeNetwork:  {Group: "network.harvesterhci.io", Version: "v1beta1", Resource: "networks"},
	ResourceTypeNetworks: {Group: "network.harvesterhci.io", Version: "v1beta1", Resource: "networks"},
	ResourceTypeImage:    {Group: "harvesterhci.io", Version: "v1beta1", Resource: "virtualmachineimages"},
	ResourceTypeImages:   {Group: "harvesterhci.io", Version: "v1beta1", Resource: "virtualmachineimages"},
}

// GVRToResourceType maps GroupVersionResource to friendly resource type names
var GVRToResourceType = map[schema.GroupVersionResource]string{
	// Core Kubernetes resources
	{Group: "", Version: "v1", Resource: "pods"}:                                          ResourceTypePod,
	{Group: "", Version: "v1", Resource: "services"}:                                      ResourceTypeService,
	{Group: "", Version: "v1", Resource: "namespaces"}:                                    ResourceTypeNamespace,
	{Group: "", Version: "v1", Resource: "nodes"}:                                         ResourceTypeNode,
	{Group: "apps", Version: "v1", Resource: "deployments"}:                               ResourceTypeDeployment,
	{Group: "apiextensions.k8s.io", Version: "v1", Resource: "customresourcedefinitions"}: ResourceTypeCRD,

	// Harvester-specific resources
	{Group: "kubevirt.io", Version: "v1", Resource: "virtualmachines"}:               ResourceTypeVM,
	{Group: "storage.harvesterhci.io", Version: "v1beta1", Resource: "volumes"}:      ResourceTypeVolume,
	{Group: "network.harvesterhci.io", Version: "v1beta1", Resource: "networks"}:     ResourceTypeNetwork,
	{Group: "harvesterhci.io", Version: "v1beta1", Resource: "virtualmachineimages"}: ResourceTypeImage,
}

```

--------------------------------------------------------------------------------
/pkg/kubernetes/resources.go:
--------------------------------------------------------------------------------

```go
package kubernetes

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/starbops/harvester-mcp-server/pkg/client"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/restmapper"
)

// ResourceHandler provides a unified interface for handling Kubernetes resources.
type ResourceHandler struct {
	client        *client.Client
	dynamicClient dynamic.Interface
	k8sClient     *kubernetes.Clientset
	mapper        *restmapper.DeferredDiscoveryRESTMapper
}

// NewResourceHandler creates a new ResourceHandler instance.
func NewResourceHandler(client *client.Client) (*ResourceHandler, error) {
	dynamicClient, err := dynamic.NewForConfig(client.Config)
	if err != nil {
		return nil, fmt.Errorf("failed to create dynamic client: %w", err)
	}

	return &ResourceHandler{
		client:        client,
		dynamicClient: dynamicClient,
		k8sClient:     client.Clientset,
	}, nil
}

// ListResources retrieves a list of resources of the specified type.
func (h *ResourceHandler) ListResources(ctx context.Context, gvr schema.GroupVersionResource, namespace string) (*unstructured.UnstructuredList, error) {
	if namespace == "" {
		return h.dynamicClient.Resource(gvr).List(ctx, metav1.ListOptions{})
	}
	return h.dynamicClient.Resource(gvr).Namespace(namespace).List(ctx, metav1.ListOptions{})
}

// GetResource retrieves a specific resource by name.
func (h *ResourceHandler) GetResource(ctx context.Context, gvr schema.GroupVersionResource, namespace, name string) (*unstructured.Unstructured, error) {
	return h.dynamicClient.Resource(gvr).Namespace(namespace).Get(ctx, name, metav1.GetOptions{})
}

// CreateResource creates a new resource.
func (h *ResourceHandler) CreateResource(ctx context.Context, gvr schema.GroupVersionResource, namespace string, obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
	return h.dynamicClient.Resource(gvr).Namespace(namespace).Create(ctx, obj, metav1.CreateOptions{})
}

// UpdateResource updates an existing resource.
func (h *ResourceHandler) UpdateResource(ctx context.Context, gvr schema.GroupVersionResource, namespace string, obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
	return h.dynamicClient.Resource(gvr).Namespace(namespace).Update(ctx, obj, metav1.UpdateOptions{})
}

// DeleteResource deletes a resource by name.
func (h *ResourceHandler) DeleteResource(ctx context.Context, gvr schema.GroupVersionResource, namespace, name string) error {
	return h.dynamicClient.Resource(gvr).Namespace(namespace).Delete(ctx, name, metav1.DeleteOptions{})
}

// IsNamespaced determines if a resource type is namespaced or cluster-scoped.
func (h *ResourceHandler) IsNamespaced(gvr schema.GroupVersionResource) (bool, error) {
	apiResourceList, err := h.k8sClient.Discovery().ServerResourcesForGroupVersion(gvr.GroupVersion().String())
	if err != nil {
		return false, err
	}

	for _, apiResource := range apiResourceList.APIResources {
		if apiResource.Name == gvr.Resource {
			return apiResource.Namespaced, nil
		}
	}

	return false, fmt.Errorf("resource %s not found in group version %s", gvr.Resource, gvr.GroupVersion().String())
}

// FormatResourceList formats a list of resources into a human-readable string based on resource type.
func (h *ResourceHandler) FormatResourceList(list *unstructured.UnstructuredList, gvr schema.GroupVersionResource) string {
	switch {
	case gvr.Resource == "pods" && gvr.Group == "":
		return formatPodList(list)
	case gvr.Resource == "services" && gvr.Group == "":
		return formatServiceList(list)
	case gvr.Resource == "namespaces" && gvr.Group == "":
		return formatNamespaceList(list)
	case gvr.Resource == "nodes" && gvr.Group == "":
		return formatNodeList(list)
	case gvr.Resource == "deployments" && gvr.Group == "apps":
		return formatDeploymentList(list)
	case gvr.Resource == "virtualmachines" && gvr.Group == "kubevirt.io":
		return formatVirtualMachineList(list)
	case gvr.Resource == "networks" && gvr.Group == "network.harvesterhci.io":
		return formatNetworkList(list)
	case gvr.Resource == "volumes" && gvr.Group == "storage.harvesterhci.io":
		return formatVolumeList(list)
	case gvr.Resource == "virtualmachineimages" && gvr.Group == "harvesterhci.io":
		return formatImageList(list)
	case gvr.Resource == "customresourcedefinitions" && gvr.Group == "apiextensions.k8s.io":
		return formatCRDList(list)
	default:
		// Generic formatter for unsupported resource types
		return formatGenericResourceList(list, gvr)
	}
}

// FormatResource formats a single resource into a human-readable string based on resource type.
func (h *ResourceHandler) FormatResource(resource *unstructured.Unstructured, gvr schema.GroupVersionResource) string {
	switch {
	case gvr.Resource == "pods" && gvr.Group == "":
		return formatPod(resource)
	case gvr.Resource == "services" && gvr.Group == "":
		return formatService(resource)
	case gvr.Resource == "namespaces" && gvr.Group == "":
		return formatNamespace(resource)
	case gvr.Resource == "nodes" && gvr.Group == "":
		return formatNode(resource)
	case gvr.Resource == "deployments" && gvr.Group == "apps":
		return formatDeployment(resource)
	case gvr.Resource == "virtualmachines" && gvr.Group == "kubevirt.io":
		return formatVirtualMachine(resource)
	case gvr.Resource == "networks" && gvr.Group == "network.harvesterhci.io":
		return formatNetwork(resource)
	case gvr.Resource == "volumes" && gvr.Group == "storage.harvesterhci.io":
		return formatVolume(resource)
	case gvr.Resource == "virtualmachineimages" && gvr.Group == "harvesterhci.io":
		return formatImage(resource)
	case gvr.Resource == "customresourcedefinitions" && gvr.Group == "apiextensions.k8s.io":
		return formatCRD(resource)
	default:
		// Generic formatter for unsupported resource types
		return formatGenericResource(resource, gvr)
	}
}

// formatGenericResourceList creates a generic human-readable representation of resources
func formatGenericResourceList(list *unstructured.UnstructuredList, gvr schema.GroupVersionResource) string {
	if len(list.Items) == 0 {
		return fmt.Sprintf("No %s found in the specified namespace(s).", gvr.Resource)
	}

	var sb strings.Builder
	sb.WriteString(fmt.Sprintf("Found %d %s:\n\n", len(list.Items), gvr.Resource))

	// Group resources by namespace if they are namespaced
	resourcesByNamespace := make(map[string][]unstructured.Unstructured)
	for _, item := range list.Items {
		namespace := item.GetNamespace()
		if namespace == "" {
			namespace = "cluster-scoped"
		}
		resourcesByNamespace[namespace] = append(resourcesByNamespace[namespace], item)
	}

	// Print resources grouped by namespace
	for namespace, items := range resourcesByNamespace {
		if namespace == "cluster-scoped" {
			sb.WriteString(fmt.Sprintf("Cluster-scoped resources (%d items)\n", len(items)))
		} else {
			sb.WriteString(fmt.Sprintf("Namespace: %s (%d items)\n", namespace, len(items)))
		}

		for _, item := range items {
			sb.WriteString(fmt.Sprintf("  • %s\n", item.GetName()))

			// Add creation time
			creationTime := item.GetCreationTimestamp().Format(time.RFC3339)
			sb.WriteString(fmt.Sprintf("    Created: %s\n", creationTime))

			// Add basic info from status if available
			status, found, _ := unstructured.NestedMap(item.Object, "status")
			if found {
				sb.WriteString("    Status:\n")
				for key, value := range status {
					sb.WriteString(fmt.Sprintf("      %s: %v\n", key, value))
				}
			}

			sb.WriteString("\n")
		}

		sb.WriteString("\n")
	}

	return sb.String()
}

// formatGenericResource creates a generic human-readable representation of a single resource
func formatGenericResource(resource *unstructured.Unstructured, gvr schema.GroupVersionResource) string {
	var sb strings.Builder

	sb.WriteString(fmt.Sprintf("%s: %s\n", strings.Title(gvr.Resource), resource.GetName()))

	if namespace := resource.GetNamespace(); namespace != "" {
		sb.WriteString(fmt.Sprintf("Namespace: %s\n", namespace))
	} else {
		sb.WriteString("Scope: Cluster-wide\n")
	}

	// Creation time
	creationTime := resource.GetCreationTimestamp().Format(time.RFC3339)
	sb.WriteString(fmt.Sprintf("Created: %s\n", creationTime))

	// Labels
	if labels := resource.GetLabels(); len(labels) > 0 {
		sb.WriteString("\nLabels:\n")
		for key, value := range labels {
			sb.WriteString(fmt.Sprintf("  %s: %s\n", key, value))
		}
	}

	// Annotations
	if annotations := resource.GetAnnotations(); len(annotations) > 0 {
		sb.WriteString("\nAnnotations:\n")
		for key, value := range annotations {
			sb.WriteString(fmt.Sprintf("  %s: %s\n", key, value))
		}
	}

	// Spec
	spec, found, _ := unstructured.NestedMap(resource.Object, "spec")
	if found {
		sb.WriteString("\nSpec:\n")
		for key, value := range spec {
			sb.WriteString(fmt.Sprintf("  %s: %v\n", key, value))
		}
	}

	// Status
	status, found, _ := unstructured.NestedMap(resource.Object, "status")
	if found {
		sb.WriteString("\nStatus:\n")
		for key, value := range status {
			sb.WriteString(fmt.Sprintf("  %s: %v\n", key, value))
		}
	}

	return sb.String()
}

// Helper function to safely get a nested string from an unstructured object
func getNestedString(obj map[string]interface{}, fields ...string) string {
	val, found, _ := unstructured.NestedString(obj, fields...)
	if !found {
		return ""
	}
	return val
}

// Helper function to safely get a nested int64 from an unstructured object
func getNestedInt64(obj map[string]interface{}, fields ...string) int64 {
	val, found, _ := unstructured.NestedInt64(obj, fields...)
	if !found {
		return 0
	}
	return val
}

// Helper function to safely get a nested bool from an unstructured object
func getNestedBool(obj map[string]interface{}, fields ...string) bool {
	val, found, _ := unstructured.NestedBool(obj, fields...)
	if !found {
		return false
	}
	return val
}

// Helper function to safely get a nested string slice from an unstructured object
func getNestedStringSlice(obj map[string]interface{}, fields ...string) []string {
	val, found, _ := unstructured.NestedStringSlice(obj, fields...)
	if !found {
		return []string{}
	}
	return val
}

// Helper function to safely get a nested map from an unstructured object
func getNestedMap(obj map[string]interface{}, fields ...string) map[string]interface{} {
	val, found, _ := unstructured.NestedMap(obj, fields...)
	if !found {
		return map[string]interface{}{}
	}
	return val
}

```

--------------------------------------------------------------------------------
/pkg/kubernetes/formatters.go:
--------------------------------------------------------------------------------

```go
package kubernetes

import (
	"fmt"
	"strings"
	"time"

	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

// ResourceFormatter defines the interface for formatting Kubernetes resources
type ResourceFormatter interface {
	FormatResource(res *unstructured.Unstructured) string
	FormatResourceList(list *unstructured.UnstructuredList) string
}

// FormatterRegistry maintains a mapping of resource kinds to their formatters
type FormatterRegistry struct {
	formatters map[string]ResourceFormatter
}

// NewFormatterRegistry creates a new registry with all registered formatters
func NewFormatterRegistry() *FormatterRegistry {
	registry := &FormatterRegistry{
		formatters: make(map[string]ResourceFormatter),
	}

	// Register core Kubernetes formatters
	registry.Register("Pod", &PodFormatter{})
	registry.Register("Service", &ServiceFormatter{})
	registry.Register("Namespace", &NamespaceFormatter{})
	registry.Register("Node", &NodeFormatter{})
	registry.Register("Deployment", &DeploymentFormatter{})

	// Register Harvester specific formatters
	registry.Register("VirtualMachine", &VirtualMachineFormatter{})
	registry.Register("Volume", &VolumeFormatter{})
	registry.Register("Network", &NetworkFormatter{})
	registry.Register("VirtualMachineImage", &VMImageFormatter{})
	registry.Register("CustomResourceDefinition", &CRDFormatter{})

	return registry
}

// defaultRegistry is a package-level registry instance for use by backward compatibility functions
var defaultRegistry = NewFormatterRegistry()

// Register adds a new formatter to the registry
func (r *FormatterRegistry) Register(kind string, formatter ResourceFormatter) {
	r.formatters[kind] = formatter
}

// GetFormatter returns the formatter for a specific resource kind
func (r *FormatterRegistry) GetFormatter(kind string) (ResourceFormatter, bool) {
	formatter, exists := r.formatters[kind]
	return formatter, exists
}

// FormatResource formats a single resource using the appropriate formatter
func (r *FormatterRegistry) FormatResource(res *unstructured.Unstructured) string {
	kind := res.GetKind()
	if formatter, exists := r.GetFormatter(kind); exists {
		return formatter.FormatResource(res)
	}
	return genericResourceFormatter(res)
}

// FormatResourceList formats a list of resources using the appropriate formatter
func (r *FormatterRegistry) FormatResourceList(list *unstructured.UnstructuredList) string {
	if len(list.Items) == 0 {
		return "No resources found in the specified namespace(s)."
	}

	// Determine the kind from the first item
	if len(list.Items) > 0 {
		kind := list.Items[0].GetKind()
		if formatter, exists := r.GetFormatter(kind); exists {
			return formatter.FormatResourceList(list)
		}
	}

	return genericResourceListFormatter(list)
}

// genericResourceFormatter creates a human-readable representation of any resource
func genericResourceFormatter(res *unstructured.Unstructured) string {
	var sb strings.Builder
	sb.WriteString(fmt.Sprintf("Kind: %s\n", res.GetKind()))
	sb.WriteString(fmt.Sprintf("Name: %s\n", res.GetName()))

	if ns := res.GetNamespace(); ns != "" {
		sb.WriteString(fmt.Sprintf("Namespace: %s\n", ns))
	}

	creationTime := res.GetCreationTimestamp().Format(time.RFC3339)
	sb.WriteString(fmt.Sprintf("Created: %s\n", creationTime))

	// Print labels if any
	if labels := res.GetLabels(); len(labels) > 0 {
		sb.WriteString("\nLabels:\n")
		for key, value := range labels {
			sb.WriteString(fmt.Sprintf("  %s: %s\n", key, value))
		}
	}

	return sb.String()
}

// genericResourceListFormatter creates a human-readable list of any resources
func genericResourceListFormatter(list *unstructured.UnstructuredList) string {
	if len(list.Items) == 0 {
		return "No resources found in the specified namespace(s)."
	}

	var sb strings.Builder
	kind := "resources"
	if len(list.Items) > 0 {
		kind = list.Items[0].GetKind() + "s"
	}

	sb.WriteString(fmt.Sprintf("Found %d %s:\n\n", len(list.Items), kind))

	// Group resources by namespace
	resourcesByNamespace := make(map[string][]unstructured.Unstructured)
	for _, item := range list.Items {
		namespace := item.GetNamespace()
		resourcesByNamespace[namespace] = append(resourcesByNamespace[namespace], item)
	}

	// Print resources grouped by namespace
	for namespace, resources := range resourcesByNamespace {
		if namespace == "" {
			sb.WriteString(fmt.Sprintf("Cluster-scoped (%d %s)\n", len(resources), kind))
		} else {
			sb.WriteString(fmt.Sprintf("Namespace: %s (%d %s)\n", namespace, len(resources), kind))
		}

		for _, resource := range resources {
			sb.WriteString(fmt.Sprintf("  • %s\n", resource.GetName()))

			// Creation time
			creationTime := resource.GetCreationTimestamp().Format(time.RFC3339)
			sb.WriteString(fmt.Sprintf("    Created: %s\n", creationTime))
			sb.WriteString("\n")
		}

		sb.WriteString("\n")
	}

	return sb.String()
}

// The following functions maintain backward compatibility with any existing code that
// may call them directly. They now use the registry pattern internally.

// FormatPodList formats a list of Pod resources in a human-readable form
func FormatPodList(list *unstructured.UnstructuredList) string {
	formatter, _ := defaultRegistry.GetFormatter("Pod")
	return formatter.FormatResourceList(list)
}

// FormatPod formats a Pod resource in a human-readable form
func FormatPod(res *unstructured.Unstructured) string {
	formatter, _ := defaultRegistry.GetFormatter("Pod")
	return formatter.FormatResource(res)
}

// FormatServiceList formats a list of Service resources in a human-readable form
func FormatServiceList(list *unstructured.UnstructuredList) string {
	formatter, _ := defaultRegistry.GetFormatter("Service")
	return formatter.FormatResourceList(list)
}

// FormatService formats a Service resource in a human-readable form
func FormatService(res *unstructured.Unstructured) string {
	formatter, _ := defaultRegistry.GetFormatter("Service")
	return formatter.FormatResource(res)
}

// FormatNamespaceList formats a list of Namespace resources in a human-readable form
func FormatNamespaceList(list *unstructured.UnstructuredList) string {
	formatter, _ := defaultRegistry.GetFormatter("Namespace")
	return formatter.FormatResourceList(list)
}

// FormatNamespace formats a Namespace resource in a human-readable form
func FormatNamespace(res *unstructured.Unstructured) string {
	formatter, _ := defaultRegistry.GetFormatter("Namespace")
	return formatter.FormatResource(res)
}

// FormatNodeList formats a list of Node resources in a human-readable form
func FormatNodeList(list *unstructured.UnstructuredList) string {
	formatter, _ := defaultRegistry.GetFormatter("Node")
	return formatter.FormatResourceList(list)
}

// FormatNode formats a Node resource in a human-readable form
func FormatNode(res *unstructured.Unstructured) string {
	formatter, _ := defaultRegistry.GetFormatter("Node")
	return formatter.FormatResource(res)
}

// FormatDeploymentList formats a list of Deployment resources in a human-readable form
func FormatDeploymentList(list *unstructured.UnstructuredList) string {
	formatter, _ := defaultRegistry.GetFormatter("Deployment")
	return formatter.FormatResourceList(list)
}

// FormatDeployment formats a Deployment resource in a human-readable form
func FormatDeployment(res *unstructured.Unstructured) string {
	formatter, _ := defaultRegistry.GetFormatter("Deployment")
	return formatter.FormatResource(res)
}

// FormatVirtualMachineList formats a list of VirtualMachine resources in a human-readable form
func FormatVirtualMachineList(list *unstructured.UnstructuredList) string {
	formatter, _ := defaultRegistry.GetFormatter("VirtualMachine")
	return formatter.FormatResourceList(list)
}

// FormatVirtualMachine formats a VirtualMachine resource in a human-readable form
func FormatVirtualMachine(res *unstructured.Unstructured) string {
	formatter, _ := defaultRegistry.GetFormatter("VirtualMachine")
	return formatter.FormatResource(res)
}

// FormatVolumeList formats a list of Volume resources in a human-readable form
func FormatVolumeList(list *unstructured.UnstructuredList) string {
	formatter, _ := defaultRegistry.GetFormatter("Volume")
	return formatter.FormatResourceList(list)
}

// FormatVolume formats a Volume resource in a human-readable form
func FormatVolume(res *unstructured.Unstructured) string {
	formatter, _ := defaultRegistry.GetFormatter("Volume")
	return formatter.FormatResource(res)
}

// FormatNetworkList formats a list of Network resources in a human-readable form
func FormatNetworkList(list *unstructured.UnstructuredList) string {
	formatter, _ := defaultRegistry.GetFormatter("Network")
	return formatter.FormatResourceList(list)
}

// FormatNetwork formats a Network resource in a human-readable form
func FormatNetwork(res *unstructured.Unstructured) string {
	formatter, _ := defaultRegistry.GetFormatter("Network")
	return formatter.FormatResource(res)
}

// FormatImageList formats a list of VirtualMachineImage resources in a human-readable form
func FormatImageList(list *unstructured.UnstructuredList) string {
	formatter, _ := defaultRegistry.GetFormatter("VirtualMachineImage")
	return formatter.FormatResourceList(list)
}

// FormatImage formats a VirtualMachineImage resource in a human-readable form
func FormatImage(res *unstructured.Unstructured) string {
	formatter, _ := defaultRegistry.GetFormatter("VirtualMachineImage")
	return formatter.FormatResource(res)
}

// FormatCRDList formats a list of CustomResourceDefinition resources in a human-readable form
func FormatCRDList(list *unstructured.UnstructuredList) string {
	formatter, _ := defaultRegistry.GetFormatter("CustomResourceDefinition")
	return formatter.FormatResourceList(list)
}

// FormatCRD formats a CustomResourceDefinition resource in a human-readable form
func FormatCRD(res *unstructured.Unstructured) string {
	formatter, _ := defaultRegistry.GetFormatter("CustomResourceDefinition")
	return formatter.FormatResource(res)
}

// For backward compatibility with any code that may be using these unexported functions
var (
	formatPodList            = FormatPodList
	formatPod                = FormatPod
	formatServiceList        = FormatServiceList
	formatService            = FormatService
	formatNamespaceList      = FormatNamespaceList
	formatNamespace          = FormatNamespace
	formatNodeList           = FormatNodeList
	formatNode               = FormatNode
	formatDeploymentList     = FormatDeploymentList
	formatDeployment         = FormatDeployment
	formatVirtualMachineList = FormatVirtualMachineList
	formatVirtualMachine     = FormatVirtualMachine
	formatVolumeList         = FormatVolumeList
	formatVolume             = FormatVolume
	formatNetworkList        = FormatNetworkList
	formatNetwork            = FormatNetwork
	formatImageList          = FormatImageList
	formatImage              = FormatImage
	formatCRDList            = FormatCRDList
	formatCRD                = FormatCRD
)

```

--------------------------------------------------------------------------------
/pkg/kubernetes/harvester_formatters.go:
--------------------------------------------------------------------------------

```go
package kubernetes

import (
	"fmt"
	"strings"
	"time"

	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

// VirtualMachineFormatter handles formatting for VirtualMachine resources
type VirtualMachineFormatter struct{}

func (f *VirtualMachineFormatter) FormatResource(res *unstructured.Unstructured) string {
	var sb strings.Builder
	sb.WriteString(fmt.Sprintf("Virtual Machine: %s\n", res.GetName()))
	sb.WriteString(fmt.Sprintf("Namespace: %s\n", res.GetNamespace()))

	// Get status
	status := "Unknown"
	running := getNestedBool(res.Object, "status", "ready")
	created := getNestedBool(res.Object, "status", "created")

	if running {
		status = "Running"
	} else if created {
		status = "Created"
	}
	sb.WriteString(fmt.Sprintf("Status: %s\n", status))

	// Running and created fields
	sb.WriteString(fmt.Sprintf("Ready: %t\n", running))
	sb.WriteString(fmt.Sprintf("Created: %t\n", created))

	// Detailed VM specification
	sb.WriteString("\nSpecification:\n")

	// CPU and Memory
	cpuCores := getNestedInt64(res.Object, "spec", "template", "spec", "domain", "cpu", "cores")
	memory := getNestedString(res.Object, "spec", "template", "spec", "domain", "resources", "requests", "memory")

	if cpuCores > 0 {
		sb.WriteString(fmt.Sprintf("  CPU Cores: %d\n", cpuCores))
	}

	if memory != "" {
		sb.WriteString(fmt.Sprintf("  Memory: %s\n", memory))
	}

	// Volumes
	volumes, _, _ := unstructured.NestedSlice(res.Object, "spec", "template", "spec", "volumes")
	if len(volumes) > 0 {
		sb.WriteString("\nVolumes:\n")
		for _, volumeObj := range volumes {
			volume, ok := volumeObj.(map[string]interface{})
			if !ok {
				continue
			}

			name, _, _ := unstructured.NestedString(volume, "name")
			sb.WriteString(fmt.Sprintf("  %s:\n", name))

			// Check different volume types
			if pvc, exists, _ := unstructured.NestedMap(volume, "persistentVolumeClaim"); exists && pvc != nil {
				claimName := getNestedString(volume, "persistentVolumeClaim", "claimName")
				sb.WriteString(fmt.Sprintf("    Type: PersistentVolumeClaim\n"))
				sb.WriteString(fmt.Sprintf("    Claim Name: %s\n", claimName))
			} else if container, exists, _ := unstructured.NestedMap(volume, "containerDisk"); exists && container != nil {
				image := getNestedString(volume, "containerDisk", "image")
				sb.WriteString(fmt.Sprintf("    Type: ContainerDisk\n"))
				sb.WriteString(fmt.Sprintf("    Image: %s\n", image))
			} else if cloudInit, exists, _ := unstructured.NestedMap(volume, "cloudInitNoCloud"); exists && cloudInit != nil {
				sb.WriteString(fmt.Sprintf("    Type: CloudInitNoCloud\n"))
				userData, userDataExists, _ := unstructured.NestedString(cloudInit, "userData")
				if userDataExists && userData != "" {
					sb.WriteString(fmt.Sprintf("    Has User Data: true\n"))
				}
				networkData, networkDataExists, _ := unstructured.NestedString(cloudInit, "networkData")
				if networkDataExists && networkData != "" {
					sb.WriteString(fmt.Sprintf("    Has Network Data: true\n"))
				}
			} else {
				sb.WriteString(fmt.Sprintf("    Type: Other\n"))
			}
		}
	}

	// Networks
	networks, _, _ := unstructured.NestedSlice(res.Object, "spec", "template", "spec", "networks")
	if len(networks) > 0 {
		sb.WriteString("\nNetworks:\n")
		for _, netObj := range networks {
			network, ok := netObj.(map[string]interface{})
			if !ok {
				continue
			}

			name, _, _ := unstructured.NestedString(network, "name")
			sb.WriteString(fmt.Sprintf("  %s:\n", name))

			// Check different network types
			if podNet, exists, _ := unstructured.NestedString(network, "pod"); exists && podNet != "" {
				sb.WriteString(fmt.Sprintf("    Type: Pod Network\n"))
			} else if multus, exists, _ := unstructured.NestedMap(network, "multus"); exists && multus != nil {
				networkName := getNestedString(network, "multus", "networkName")
				sb.WriteString(fmt.Sprintf("    Type: Multus\n"))
				sb.WriteString(fmt.Sprintf("    Network Name: %s\n", networkName))
			} else {
				sb.WriteString(fmt.Sprintf("    Type: Other\n"))
			}
		}
	}

	// Creation time
	creationTime := res.GetCreationTimestamp().Format(time.RFC3339)
	sb.WriteString(fmt.Sprintf("\nCreated: %s\n", creationTime))

	return sb.String()
}

func (f *VirtualMachineFormatter) FormatResourceList(list *unstructured.UnstructuredList) string {
	if len(list.Items) == 0 {
		return "No virtual machines found in the specified namespace(s)."
	}

	var sb strings.Builder
	sb.WriteString(fmt.Sprintf("Found %d virtual machine(s):\n\n", len(list.Items)))

	// Group VMs by namespace
	vmsByNamespace := make(map[string][]unstructured.Unstructured)
	for _, item := range list.Items {
		namespace := item.GetNamespace()
		vmsByNamespace[namespace] = append(vmsByNamespace[namespace], item)
	}

	// Print VMs grouped by namespace
	for namespace, vms := range vmsByNamespace {
		sb.WriteString(fmt.Sprintf("Namespace: %s (%d VMs)\n", namespace, len(vms)))

		for _, vm := range vms {
			// Get status
			status := "Unknown"
			running := getNestedBool(vm.Object, "status", "ready")
			created := getNestedBool(vm.Object, "status", "created")

			if running {
				status = "Running"
			} else if created {
				status = "Created"
			}

			// Get spec details
			cpuCores := getNestedInt64(vm.Object, "spec", "template", "spec", "domain", "cpu", "cores")
			memory := getNestedString(vm.Object, "spec", "template", "spec", "domain", "resources", "requests", "memory")

			// Basic VM info
			sb.WriteString(fmt.Sprintf("  • %s\n", vm.GetName()))
			sb.WriteString(fmt.Sprintf("    Status: %s\n", status))

			if cpuCores > 0 {
				sb.WriteString(fmt.Sprintf("    CPU Cores: %d\n", cpuCores))
			}

			if memory != "" {
				sb.WriteString(fmt.Sprintf("    Memory: %s\n", memory))
			}

			// Volumes
			volumes, _, _ := unstructured.NestedSlice(vm.Object, "spec", "template", "spec", "volumes")
			if len(volumes) > 0 {
				sb.WriteString("    Volumes:\n")
				for _, volumeObj := range volumes {
					volume, ok := volumeObj.(map[string]interface{})
					if !ok {
						continue
					}

					name, _, _ := unstructured.NestedString(volume, "name")

					// Check different volume types
					if pvc, exists, _ := unstructured.NestedMap(volume, "persistentVolumeClaim"); exists && pvc != nil {
						claimName := getNestedString(volume, "persistentVolumeClaim", "claimName")
						sb.WriteString(fmt.Sprintf("      %s: PVC %s\n", name, claimName))
					} else if container, exists, _ := unstructured.NestedMap(volume, "containerDisk"); exists && container != nil {
						image := getNestedString(volume, "containerDisk", "image")
						sb.WriteString(fmt.Sprintf("      %s: ContainerDisk %s\n", name, image))
					} else if cloudInit, exists, _ := unstructured.NestedMap(volume, "cloudInitNoCloud"); exists && cloudInit != nil {
						sb.WriteString(fmt.Sprintf("      %s: CloudInit\n", name))
					} else {
						sb.WriteString(fmt.Sprintf("      %s\n", name))
					}
				}
			}

			// Networks
			networks, _, _ := unstructured.NestedSlice(vm.Object, "spec", "template", "spec", "networks")
			if len(networks) > 0 {
				sb.WriteString("    Networks:\n")
				for _, netObj := range networks {
					network, ok := netObj.(map[string]interface{})
					if !ok {
						continue
					}

					name, _, _ := unstructured.NestedString(network, "name")

					// Check different network types
					if podNet, exists, _ := unstructured.NestedString(network, "pod"); exists && podNet != "" {
						sb.WriteString(fmt.Sprintf("      %s: Pod Network\n", name))
					} else if multus, exists, _ := unstructured.NestedMap(network, "multus"); exists && multus != nil {
						networkName := getNestedString(network, "multus", "networkName")
						sb.WriteString(fmt.Sprintf("      %s: Multus %s\n", name, networkName))
					} else {
						sb.WriteString(fmt.Sprintf("      %s\n", name))
					}
				}
			}

			// Creation time
			creationTime := vm.GetCreationTimestamp().Format(time.RFC3339)
			sb.WriteString(fmt.Sprintf("    Created: %s\n", creationTime))

			sb.WriteString("\n")
		}

		sb.WriteString("\n")
	}

	return sb.String()
}

// VolumeFormatter handles formatting for Volume resources
type VolumeFormatter struct{}

func (f *VolumeFormatter) FormatResource(res *unstructured.Unstructured) string {
	var sb strings.Builder
	sb.WriteString(fmt.Sprintf("Volume: %s\n", res.GetName()))
	sb.WriteString(fmt.Sprintf("Namespace: %s\n", res.GetNamespace()))

	// Get size and status
	size := getNestedString(res.Object, "spec", "size")
	status := getNestedString(res.Object, "status", "state")

	if status != "" {
		sb.WriteString(fmt.Sprintf("Status: %s\n", status))
	}
	if size != "" {
		sb.WriteString(fmt.Sprintf("Size: %s\n", size))
	}

	// Storage Class
	storageClass := getNestedString(res.Object, "spec", "storageClassName")
	if storageClass != "" {
		sb.WriteString(fmt.Sprintf("Storage Class: %s\n", storageClass))
	}

	// Additional details
	accessModes, _, _ := unstructured.NestedStringSlice(res.Object, "spec", "accessModes")
	if len(accessModes) > 0 {
		sb.WriteString("\nAccess Modes:\n")
		for _, mode := range accessModes {
			sb.WriteString(fmt.Sprintf("  %s\n", mode))
		}
	}

	// Creation time
	creationTime := res.GetCreationTimestamp().Format(time.RFC3339)
	sb.WriteString(fmt.Sprintf("\nCreated: %s\n", creationTime))

	return sb.String()
}

func (f *VolumeFormatter) FormatResourceList(list *unstructured.UnstructuredList) string {
	if len(list.Items) == 0 {
		return "No volumes found in the specified namespace(s)."
	}

	var sb strings.Builder
	sb.WriteString(fmt.Sprintf("Found %d volume(s):\n\n", len(list.Items)))

	// Group volumes by namespace
	volumesByNamespace := make(map[string][]unstructured.Unstructured)
	for _, item := range list.Items {
		namespace := item.GetNamespace()
		volumesByNamespace[namespace] = append(volumesByNamespace[namespace], item)
	}

	// Print volumes grouped by namespace
	for namespace, volumes := range volumesByNamespace {
		sb.WriteString(fmt.Sprintf("Namespace: %s (%d volumes)\n", namespace, len(volumes)))

		for _, volume := range volumes {
			// Get size and status
			size := getNestedString(volume.Object, "spec", "size")
			status := getNestedString(volume.Object, "status", "state")

			// Basic volume info
			sb.WriteString(fmt.Sprintf("  • %s\n", volume.GetName()))
			if status != "" {
				sb.WriteString(fmt.Sprintf("    Status: %s\n", status))
			}
			if size != "" {
				sb.WriteString(fmt.Sprintf("    Size: %s\n", size))
			}

			// Creation time
			creationTime := volume.GetCreationTimestamp().Format(time.RFC3339)
			sb.WriteString(fmt.Sprintf("    Created: %s\n", creationTime))

			sb.WriteString("\n")
		}

		sb.WriteString("\n")
	}

	return sb.String()
}

// NetworkFormatter handles formatting for Network resources
type NetworkFormatter struct{}

func (f *NetworkFormatter) FormatResource(res *unstructured.Unstructured) string {
	var sb strings.Builder
	sb.WriteString(fmt.Sprintf("Network: %s\n", res.GetName()))
	sb.WriteString(fmt.Sprintf("Namespace: %s\n", res.GetNamespace()))

	// Get network type and config
	networkType := getNestedString(res.Object, "spec", "type")
	if networkType != "" {
		sb.WriteString(fmt.Sprintf("Type: %s\n", networkType))
	}

	// Config details
	config, configFound, _ := unstructured.NestedMap(res.Object, "spec", "config")
	if configFound && len(config) > 0 {
		sb.WriteString("\nConfiguration:\n")
		for key, value := range config {
			sb.WriteString(fmt.Sprintf("  %s: %v\n", key, value))
		}
	}

	// Creation time
	creationTime := res.GetCreationTimestamp().Format(time.RFC3339)
	sb.WriteString(fmt.Sprintf("\nCreated: %s\n", creationTime))

	return sb.String()
}

func (f *NetworkFormatter) FormatResourceList(list *unstructured.UnstructuredList) string {
	if len(list.Items) == 0 {
		return "No networks found in the specified namespace(s)."
	}

	var sb strings.Builder
	sb.WriteString(fmt.Sprintf("Found %d network(s):\n\n", len(list.Items)))

	// Group networks by namespace
	networksByNamespace := make(map[string][]unstructured.Unstructured)
	for _, item := range list.Items {
		namespace := item.GetNamespace()
		networksByNamespace[namespace] = append(networksByNamespace[namespace], item)
	}

	// Print networks grouped by namespace
	for namespace, networks := range networksByNamespace {
		sb.WriteString(fmt.Sprintf("Namespace: %s (%d networks)\n", namespace, len(networks)))

		for _, network := range networks {
			// Get type and config
			networkType := getNestedString(network.Object, "spec", "type")

			// Basic network info
			sb.WriteString(fmt.Sprintf("  • %s\n", network.GetName()))
			if networkType != "" {
				sb.WriteString(fmt.Sprintf("    Type: %s\n", networkType))
			}

			// VLAN ID if present
			vlanId := getNestedInt64(network.Object, "spec", "config", "vlan")
			if vlanId > 0 {
				sb.WriteString(fmt.Sprintf("    VLAN ID: %d\n", vlanId))
			}

			// Creation time
			creationTime := network.GetCreationTimestamp().Format(time.RFC3339)
			sb.WriteString(fmt.Sprintf("    Created: %s\n", creationTime))

			sb.WriteString("\n")
		}

		sb.WriteString("\n")
	}

	return sb.String()
}

// VMImageFormatter handles formatting for VirtualMachineImage resources
type VMImageFormatter struct{}

func (f *VMImageFormatter) FormatResource(res *unstructured.Unstructured) string {
	var sb strings.Builder
	sb.WriteString(fmt.Sprintf("VM Image: %s\n", res.GetName()))
	sb.WriteString(fmt.Sprintf("Namespace: %s\n", res.GetNamespace()))

	// Get image details
	displayName := getNestedString(res.Object, "spec", "displayName")
	url := getNestedString(res.Object, "spec", "url")
	description := getNestedString(res.Object, "spec", "description")

	if displayName != "" {
		sb.WriteString(fmt.Sprintf("Display Name: %s\n", displayName))
	}
	if url != "" {
		sb.WriteString(fmt.Sprintf("URL: %s\n", url))
	}
	if description != "" {
		sb.WriteString(fmt.Sprintf("Description: %s\n", description))
	}

	// Status details
	status, statusFound, _ := unstructured.NestedMap(res.Object, "status")
	if statusFound && len(status) > 0 {
		sb.WriteString("\nStatus:\n")

		state := getNestedString(res.Object, "status", "state")
		if state != "" {
			sb.WriteString(fmt.Sprintf("  State: %s\n", state))
		}

		progress := getNestedString(res.Object, "status", "progress")
		if progress != "" {
			sb.WriteString(fmt.Sprintf("  Progress: %s\n", progress))
		}

		size := getNestedString(res.Object, "status", "size")
		if size != "" {
			sb.WriteString(fmt.Sprintf("  Size: %s\n", size))
		}
	}

	// Creation time
	creationTime := res.GetCreationTimestamp().Format(time.RFC3339)
	sb.WriteString(fmt.Sprintf("\nCreated: %s\n", creationTime))

	return sb.String()
}

func (f *VMImageFormatter) FormatResourceList(list *unstructured.UnstructuredList) string {
	if len(list.Items) == 0 {
		return "No VM images found in the specified namespace(s)."
	}

	var sb strings.Builder
	sb.WriteString(fmt.Sprintf("Found %d VM image(s):\n\n", len(list.Items)))

	// Group images by namespace
	imagesByNamespace := make(map[string][]unstructured.Unstructured)
	for _, item := range list.Items {
		namespace := item.GetNamespace()
		imagesByNamespace[namespace] = append(imagesByNamespace[namespace], item)
	}

	// Print images grouped by namespace
	for namespace, images := range imagesByNamespace {
		sb.WriteString(fmt.Sprintf("Namespace: %s (%d images)\n", namespace, len(images)))

		for _, image := range images {
			// Get basic info
			url := getNestedString(image.Object, "spec", "displayName")
			if url == "" {
				url = getNestedString(image.Object, "spec", "url")
			}

			size := getNestedString(image.Object, "status", "size")
			progress := getNestedString(image.Object, "status", "progress")

			// Basic image info
			sb.WriteString(fmt.Sprintf("  • %s\n", image.GetName()))
			if url != "" {
				sb.WriteString(fmt.Sprintf("    Source: %s\n", url))
			}
			if size != "" {
				sb.WriteString(fmt.Sprintf("    Size: %s\n", size))
			}
			if progress != "" {
				sb.WriteString(fmt.Sprintf("    Progress: %s\n", progress))
			}

			// Creation time
			creationTime := image.GetCreationTimestamp().Format(time.RFC3339)
			sb.WriteString(fmt.Sprintf("    Created: %s\n", creationTime))

			sb.WriteString("\n")
		}

		sb.WriteString("\n")
	}

	return sb.String()
}

// CRDFormatter handles formatting for CustomResourceDefinition resources
type CRDFormatter struct{}

func (f *CRDFormatter) FormatResource(res *unstructured.Unstructured) string {
	var sb strings.Builder
	sb.WriteString(fmt.Sprintf("Custom Resource Definition: %s\n", res.GetName()))

	// Get CRD details
	group := getNestedString(res.Object, "spec", "group")
	kind := getNestedString(res.Object, "spec", "names", "kind")
	plural := getNestedString(res.Object, "spec", "names", "plural")
	scope := getNestedString(res.Object, "spec", "scope")

	sb.WriteString(fmt.Sprintf("Group: %s\n", group))
	sb.WriteString(fmt.Sprintf("Kind: %s\n", kind))
	sb.WriteString(fmt.Sprintf("Plural: %s\n", plural))
	sb.WriteString(fmt.Sprintf("Scope: %s\n", scope))

	// Short names
	shortNames, _, _ := unstructured.NestedStringSlice(res.Object, "spec", "names", "shortNames")
	if len(shortNames) > 0 {
		sb.WriteString("Short Names: ")
		for i, name := range shortNames {
			if i > 0 {
				sb.WriteString(", ")
			}
			sb.WriteString(name)
		}
		sb.WriteString("\n")
	}

	// Versions
	versions, _, _ := unstructured.NestedSlice(res.Object, "spec", "versions")
	if len(versions) > 0 {
		sb.WriteString("\nVersions:\n")
		for _, versionObj := range versions {
			version, ok := versionObj.(map[string]interface{})
			if !ok {
				continue
			}

			name, _, _ := unstructured.NestedString(version, "name")
			served, _, _ := unstructured.NestedBool(version, "served")
			storage, _, _ := unstructured.NestedBool(version, "storage")

			sb.WriteString(fmt.Sprintf("  %s:\n", name))
			sb.WriteString(fmt.Sprintf("    Served: %t\n", served))
			sb.WriteString(fmt.Sprintf("    Storage: %t\n", storage))

			// Schema details if present
			schema, schemaFound, _ := unstructured.NestedMap(version, "schema", "openAPIV3Schema")
			if schemaFound && len(schema) > 0 {
				sb.WriteString("    Schema: Available\n")
			}
		}
	}

	// Creation time
	creationTime := res.GetCreationTimestamp().Format(time.RFC3339)
	sb.WriteString(fmt.Sprintf("\nCreated: %s\n", creationTime))

	return sb.String()
}

func (f *CRDFormatter) FormatResourceList(list *unstructured.UnstructuredList) string {
	if len(list.Items) == 0 {
		return "No custom resource definitions found."
	}

	var sb strings.Builder
	sb.WriteString(fmt.Sprintf("Found %d custom resource definition(s):\n\n", len(list.Items)))

	// Group CRDs by group
	crdsByGroup := make(map[string][]unstructured.Unstructured)
	for _, item := range list.Items {
		group := getNestedString(item.Object, "spec", "group")
		if group == "" {
			group = "core"
		}
		crdsByGroup[group] = append(crdsByGroup[group], item)
	}

	// Print CRDs grouped by group
	for group, crds := range crdsByGroup {
		sb.WriteString(fmt.Sprintf("Group: %s (%d CRDs)\n", group, len(crds)))

		for _, crd := range crds {
			// Get basic info
			kind := getNestedString(crd.Object, "spec", "names", "kind")
			plural := getNestedString(crd.Object, "spec", "names", "plural")

			// Basic CRD info
			sb.WriteString(fmt.Sprintf("  • %s\n", crd.GetName()))
			sb.WriteString(fmt.Sprintf("    Kind: %s\n", kind))
			sb.WriteString(fmt.Sprintf("    Plural: %s\n", plural))

			// Versions
			versions, _, _ := unstructured.NestedSlice(crd.Object, "spec", "versions")
			if len(versions) > 0 {
				sb.WriteString("    Versions:\n")
				for _, versionObj := range versions {
					version, ok := versionObj.(map[string]interface{})
					if !ok {
						continue
					}

					name, _, _ := unstructured.NestedString(version, "name")
					served, _, _ := unstructured.NestedBool(version, "served")
					storage, _, _ := unstructured.NestedBool(version, "storage")

					sb.WriteString(fmt.Sprintf("      %s (served: %t, storage: %t)\n", name, served, storage))
				}
			}

			// Creation time
			creationTime := crd.GetCreationTimestamp().Format(time.RFC3339)
			sb.WriteString(fmt.Sprintf("    Created: %s\n", creationTime))

			sb.WriteString("\n")
		}

		sb.WriteString("\n")
	}

	return sb.String()
}

```

--------------------------------------------------------------------------------
/pkg/mcp/server.go:
--------------------------------------------------------------------------------

```go
package mcp

import (
	"context"
	"fmt"

	"github.com/mark3labs/mcp-go/mcp"
	"github.com/mark3labs/mcp-go/server"
	log "github.com/sirupsen/logrus"
	"github.com/starbops/harvester-mcp-server/pkg/client"
	"github.com/starbops/harvester-mcp-server/pkg/kubernetes"
)

// Config represents the configuration for the Harvester MCP server.
type Config struct {
	// KubeConfigPath is the path to the kubeconfig file.
	KubeConfigPath string
}

// HarvesterMCPServer represents the MCP server for Harvester HCI.
type HarvesterMCPServer struct {
	mcpServer       *server.MCPServer
	k8sClient       *client.Client
	resourceHandler *kubernetes.ResourceHandler
}

// NewServer creates a new Harvester MCP server.
func NewServer(cfg *Config) (*HarvesterMCPServer, error) {
	// Create client configuration
	clientCfg := &client.Config{
		KubeConfigPath: cfg.KubeConfigPath,
	}

	// Create Kubernetes client
	k8sClient, err := client.NewClient(clientCfg)
	if err != nil {
		return nil, fmt.Errorf("failed to create Kubernetes client: %w", err)
	}

	// Create resource handler
	resourceHandler, err := kubernetes.NewResourceHandler(k8sClient)
	if err != nil {
		return nil, fmt.Errorf("failed to create resource handler: %w", err)
	}

	// Create a new MCP server
	mcpServer := server.NewMCPServer(
		"Harvester MCP Server",
		"1.0.0",
	)

	harvesterServer := &HarvesterMCPServer{
		mcpServer:       mcpServer,
		k8sClient:       k8sClient,
		resourceHandler: resourceHandler,
	}

	// Register tools
	harvesterServer.registerTools()

	return harvesterServer, nil
}

// ServeStdio starts the MCP server using stdio.
func (s *HarvesterMCPServer) ServeStdio() error {
	log.Info("Starting Harvester MCP server...")
	return server.ServeStdio(s.mcpServer)
}

// registerTools registers all the tools with the MCP server.
func (s *HarvesterMCPServer) registerTools() {
	// Register Kubernetes common tools
	s.registerKubernetesPodTools()
	s.registerKubernetesDeploymentTools()
	s.registerKubernetesServiceTools()
	s.registerKubernetesNamespaceTools()
	s.registerKubernetesNodeTools()
	s.registerKubernetesCRDTools()

	// Register Harvester-specific tools
	s.registerHarvesterVirtualMachineTools()
	s.registerHarvesterImageTools()
	s.registerHarvesterVolumeTools()
	s.registerHarvesterNetworkTools()
}

// registerKubernetesPodTools registers Pod-related tools.
func (s *HarvesterMCPServer) registerKubernetesPodTools() {
	// List pods tool
	listPodsTool := mcp.NewTool(
		"list_pods",
		mcp.WithDescription("List pods in the Harvester cluster"),
		mcp.WithString("namespace",
			mcp.Description("The namespace to list pods from (optional, defaults to all namespaces)"),
		),
	)
	s.mcpServer.AddTool(listPodsTool, func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
		namespace, _ := req.Params.Arguments["namespace"].(string)

		// Use the unified resource handler
		gvr := kubernetes.ResourceTypeToGVR[kubernetes.ResourceTypePods]
		list, err := s.resourceHandler.ListResources(ctx, gvr, namespace)
		if err != nil {
			return mcp.NewToolResultError(fmt.Sprintf("Failed to list pods: %v", err)), nil
		}

		// Format the list using the resource formatter
		formatted := s.resourceHandler.FormatResourceList(list, gvr)
		return mcp.NewToolResultText(formatted), nil
	})

	// Get pod tool
	getPodTool := mcp.NewTool(
		"get_pod",
		mcp.WithDescription("Get pod details from the Harvester cluster"),
		mcp.WithString("namespace",
			mcp.Required(),
			mcp.Description("The namespace of the pod"),
		),
		mcp.WithString("name",
			mcp.Required(),
			mcp.Description("The name of the pod"),
		),
	)
	s.mcpServer.AddTool(getPodTool, func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
		namespace, ok := req.Params.Arguments["namespace"].(string)
		if !ok || namespace == "" {
			return mcp.NewToolResultError("Namespace is required"), nil
		}

		name, ok := req.Params.Arguments["name"].(string)
		if !ok || name == "" {
			return mcp.NewToolResultError("Pod name is required"), nil
		}

		// Use the unified resource handler
		gvr := kubernetes.ResourceTypeToGVR[kubernetes.ResourceTypePod]
		resource, err := s.resourceHandler.GetResource(ctx, gvr, namespace, name)
		if err != nil {
			return mcp.NewToolResultError(fmt.Sprintf("Failed to get pod %s in namespace %s: %v", name, namespace, err)), nil
		}

		// Format the resource using the resource formatter
		formatted := s.resourceHandler.FormatResource(resource, gvr)
		return mcp.NewToolResultText(formatted), nil
	})

	// Delete pod tool
	deletePodTool := mcp.NewTool(
		"delete_pod",
		mcp.WithDescription("Delete a pod from the Harvester cluster"),
		mcp.WithString("namespace",
			mcp.Required(),
			mcp.Description("The namespace of the pod"),
		),
		mcp.WithString("name",
			mcp.Required(),
			mcp.Description("The name of the pod to delete"),
		),
	)
	s.mcpServer.AddTool(deletePodTool, func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
		namespace, ok := req.Params.Arguments["namespace"].(string)
		if !ok || namespace == "" {
			return mcp.NewToolResultError("Namespace is required"), nil
		}

		name, ok := req.Params.Arguments["name"].(string)
		if !ok || name == "" {
			return mcp.NewToolResultError("Pod name is required"), nil
		}

		// Use the unified resource handler
		gvr := kubernetes.ResourceTypeToGVR[kubernetes.ResourceTypePod]
		err := s.resourceHandler.DeleteResource(ctx, gvr, namespace, name)
		if err != nil {
			return mcp.NewToolResultError(fmt.Sprintf("Failed to delete pod %s in namespace %s: %v", name, namespace, err)), nil
		}

		return mcp.NewToolResultText(fmt.Sprintf("Pod %s in namespace %s deleted successfully", name, namespace)), nil
	})
}

// registerKubernetesDeploymentTools registers Deployment-related tools.
func (s *HarvesterMCPServer) registerKubernetesDeploymentTools() {
	// List deployments tool
	listDeploymentsTool := mcp.NewTool(
		"list_deployments",
		mcp.WithDescription("List deployments in the Harvester cluster"),
		mcp.WithString("namespace",
			mcp.Description("The namespace to list deployments from (optional, defaults to all namespaces)"),
		),
	)
	s.mcpServer.AddTool(listDeploymentsTool, func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
		namespace, _ := req.Params.Arguments["namespace"].(string)

		// Use the unified resource handler
		gvr := kubernetes.ResourceTypeToGVR[kubernetes.ResourceTypeDeployments]
		list, err := s.resourceHandler.ListResources(ctx, gvr, namespace)
		if err != nil {
			return mcp.NewToolResultError(fmt.Sprintf("Failed to list deployments: %v", err)), nil
		}

		// Format the list using the resource formatter
		formatted := s.resourceHandler.FormatResourceList(list, gvr)
		return mcp.NewToolResultText(formatted), nil
	})

	// Get deployment tool
	getDeploymentTool := mcp.NewTool(
		"get_deployment",
		mcp.WithDescription("Get deployment details from the Harvester cluster"),
		mcp.WithString("namespace",
			mcp.Required(),
			mcp.Description("The namespace of the deployment"),
		),
		mcp.WithString("name",
			mcp.Required(),
			mcp.Description("The name of the deployment"),
		),
	)
	s.mcpServer.AddTool(getDeploymentTool, func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
		namespace, ok := req.Params.Arguments["namespace"].(string)
		if !ok || namespace == "" {
			return mcp.NewToolResultError("Namespace is required"), nil
		}

		name, ok := req.Params.Arguments["name"].(string)
		if !ok || name == "" {
			return mcp.NewToolResultError("Deployment name is required"), nil
		}

		// Use the unified resource handler
		gvr := kubernetes.ResourceTypeToGVR[kubernetes.ResourceTypeDeployment]
		resource, err := s.resourceHandler.GetResource(ctx, gvr, namespace, name)
		if err != nil {
			return mcp.NewToolResultError(fmt.Sprintf("Failed to get deployment %s in namespace %s: %v", name, namespace, err)), nil
		}

		// Format the resource using the resource formatter
		formatted := s.resourceHandler.FormatResource(resource, gvr)
		return mcp.NewToolResultText(formatted), nil
	})
}

// registerKubernetesServiceTools registers Service-related tools.
func (s *HarvesterMCPServer) registerKubernetesServiceTools() {
	// List services tool
	listServicesTool := mcp.NewTool(
		"list_services",
		mcp.WithDescription("List services in the Harvester cluster"),
		mcp.WithString("namespace",
			mcp.Description("The namespace to list services from (optional, defaults to all namespaces)"),
		),
	)
	s.mcpServer.AddTool(listServicesTool, func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
		namespace, _ := req.Params.Arguments["namespace"].(string)

		// Use the unified resource handler
		gvr := kubernetes.ResourceTypeToGVR[kubernetes.ResourceTypeServices]
		list, err := s.resourceHandler.ListResources(ctx, gvr, namespace)
		if err != nil {
			return mcp.NewToolResultError(fmt.Sprintf("Failed to list services: %v", err)), nil
		}

		// Format the list using the resource formatter
		formatted := s.resourceHandler.FormatResourceList(list, gvr)
		return mcp.NewToolResultText(formatted), nil
	})

	// Get service tool
	getServiceTool := mcp.NewTool(
		"get_service",
		mcp.WithDescription("Get service details from the Harvester cluster"),
		mcp.WithString("namespace",
			mcp.Required(),
			mcp.Description("The namespace of the service"),
		),
		mcp.WithString("name",
			mcp.Required(),
			mcp.Description("The name of the service"),
		),
	)
	s.mcpServer.AddTool(getServiceTool, func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
		namespace, ok := req.Params.Arguments["namespace"].(string)
		if !ok || namespace == "" {
			return mcp.NewToolResultError("Namespace is required"), nil
		}

		name, ok := req.Params.Arguments["name"].(string)
		if !ok || name == "" {
			return mcp.NewToolResultError("Service name is required"), nil
		}

		// Use the unified resource handler
		gvr := kubernetes.ResourceTypeToGVR[kubernetes.ResourceTypeService]
		resource, err := s.resourceHandler.GetResource(ctx, gvr, namespace, name)
		if err != nil {
			return mcp.NewToolResultError(fmt.Sprintf("Failed to get service %s in namespace %s: %v", name, namespace, err)), nil
		}

		// Format the resource using the resource formatter
		formatted := s.resourceHandler.FormatResource(resource, gvr)
		return mcp.NewToolResultText(formatted), nil
	})
}

// registerKubernetesNamespaceTools registers Namespace-related tools.
func (s *HarvesterMCPServer) registerKubernetesNamespaceTools() {
	// List namespaces tool
	listNamespacesTool := mcp.NewTool(
		"list_namespaces",
		mcp.WithDescription("List namespaces in the Harvester cluster"),
	)
	s.mcpServer.AddTool(listNamespacesTool, func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
		// Use the unified resource handler
		gvr := kubernetes.ResourceTypeToGVR[kubernetes.ResourceTypeNamespaces]
		list, err := s.resourceHandler.ListResources(ctx, gvr, "")
		if err != nil {
			return mcp.NewToolResultError(fmt.Sprintf("Failed to list namespaces: %v", err)), nil
		}

		// Format the list using the resource formatter
		formatted := s.resourceHandler.FormatResourceList(list, gvr)
		return mcp.NewToolResultText(formatted), nil
	})

	// Get namespace tool
	getNamespaceTool := mcp.NewTool(
		"get_namespace",
		mcp.WithDescription("Get namespace details from the Harvester cluster"),
		mcp.WithString("name",
			mcp.Required(),
			mcp.Description("The name of the namespace"),
		),
	)
	s.mcpServer.AddTool(getNamespaceTool, func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
		name, ok := req.Params.Arguments["name"].(string)
		if !ok || name == "" {
			return mcp.NewToolResultError("Namespace name is required"), nil
		}

		// Use the unified resource handler
		gvr := kubernetes.ResourceTypeToGVR[kubernetes.ResourceTypeNamespace]
		resource, err := s.resourceHandler.GetResource(ctx, gvr, "", name)
		if err != nil {
			return mcp.NewToolResultError(fmt.Sprintf("Failed to get namespace %s: %v", name, err)), nil
		}

		// Format the resource using the resource formatter
		formatted := s.resourceHandler.FormatResource(resource, gvr)
		return mcp.NewToolResultText(formatted), nil
	})
}

// registerKubernetesNodeTools registers Node-related tools.
func (s *HarvesterMCPServer) registerKubernetesNodeTools() {
	// List nodes tool
	listNodesTool := mcp.NewTool(
		"list_nodes",
		mcp.WithDescription("List nodes in the Harvester cluster"),
	)
	s.mcpServer.AddTool(listNodesTool, func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
		// Use the unified resource handler
		gvr := kubernetes.ResourceTypeToGVR[kubernetes.ResourceTypeNodes]
		list, err := s.resourceHandler.ListResources(ctx, gvr, "")
		if err != nil {
			return mcp.NewToolResultError(fmt.Sprintf("Failed to list nodes: %v", err)), nil
		}

		// Format the list using the resource formatter
		formatted := s.resourceHandler.FormatResourceList(list, gvr)
		return mcp.NewToolResultText(formatted), nil
	})

	// Get node tool
	getNodeTool := mcp.NewTool(
		"get_node",
		mcp.WithDescription("Get node details from the Harvester cluster"),
		mcp.WithString("name",
			mcp.Required(),
			mcp.Description("The name of the node"),
		),
	)
	s.mcpServer.AddTool(getNodeTool, func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
		name, ok := req.Params.Arguments["name"].(string)
		if !ok || name == "" {
			return mcp.NewToolResultError("Node name is required"), nil
		}

		// Use the unified resource handler
		gvr := kubernetes.ResourceTypeToGVR[kubernetes.ResourceTypeNode]
		resource, err := s.resourceHandler.GetResource(ctx, gvr, "", name)
		if err != nil {
			return mcp.NewToolResultError(fmt.Sprintf("Failed to get node %s: %v", name, err)), nil
		}

		// Format the resource using the resource formatter
		formatted := s.resourceHandler.FormatResource(resource, gvr)
		return mcp.NewToolResultText(formatted), nil
	})
}

// registerKubernetesCRDTools registers CRD-related tools.
func (s *HarvesterMCPServer) registerKubernetesCRDTools() {
	// List CRDs tool
	listCRDsTool := mcp.NewTool(
		"list_crds",
		mcp.WithDescription("List Custom Resource Definitions in the Harvester cluster"),
	)
	s.mcpServer.AddTool(listCRDsTool, func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
		// Use the unified resource handler
		gvr := kubernetes.ResourceTypeToGVR[kubernetes.ResourceTypeCRDs]
		list, err := s.resourceHandler.ListResources(ctx, gvr, "")
		if err != nil {
			return mcp.NewToolResultError(fmt.Sprintf("Failed to list CRDs: %v", err)), nil
		}

		// Format the list using the resource formatter
		formatted := s.resourceHandler.FormatResourceList(list, gvr)
		return mcp.NewToolResultText(formatted), nil
	})
}

// registerHarvesterVirtualMachineTools registers Harvester VM-related tools.
func (s *HarvesterMCPServer) registerHarvesterVirtualMachineTools() {
	// List VMs tool
	listVMsTool := mcp.NewTool(
		"list_vms",
		mcp.WithDescription("List Virtual Machines in the Harvester cluster"),
		mcp.WithString("namespace",
			mcp.Description("The namespace to list VMs from (optional, defaults to all namespaces)"),
		),
	)
	s.mcpServer.AddTool(listVMsTool, func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
		namespace, _ := req.Params.Arguments["namespace"].(string)

		// Use the unified resource handler
		gvr := kubernetes.ResourceTypeToGVR[kubernetes.ResourceTypeVMs]
		list, err := s.resourceHandler.ListResources(ctx, gvr, namespace)
		if err != nil {
			return mcp.NewToolResultError(fmt.Sprintf("Failed to list VMs: %v", err)), nil
		}

		// Format the list using the resource formatter
		formatted := s.resourceHandler.FormatResourceList(list, gvr)
		return mcp.NewToolResultText(formatted), nil
	})

	// Get VM tool
	getVMTool := mcp.NewTool(
		"get_vm",
		mcp.WithDescription("Get Virtual Machine details from the Harvester cluster"),
		mcp.WithString("namespace",
			mcp.Required(),
			mcp.Description("The namespace of the VM"),
		),
		mcp.WithString("name",
			mcp.Required(),
			mcp.Description("The name of the VM"),
		),
	)
	s.mcpServer.AddTool(getVMTool, func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
		namespace, ok := req.Params.Arguments["namespace"].(string)
		if !ok || namespace == "" {
			return mcp.NewToolResultError("Namespace is required"), nil
		}

		name, ok := req.Params.Arguments["name"].(string)
		if !ok || name == "" {
			return mcp.NewToolResultError("VM name is required"), nil
		}

		// Use the unified resource handler
		gvr := kubernetes.ResourceTypeToGVR[kubernetes.ResourceTypeVM]
		resource, err := s.resourceHandler.GetResource(ctx, gvr, namespace, name)
		if err != nil {
			return mcp.NewToolResultError(fmt.Sprintf("Failed to get VM %s in namespace %s: %v", name, namespace, err)), nil
		}

		// Format the resource using the resource formatter
		formatted := s.resourceHandler.FormatResource(resource, gvr)
		return mcp.NewToolResultText(formatted), nil
	})
}

// registerHarvesterImageTools registers Harvester Image-related tools.
func (s *HarvesterMCPServer) registerHarvesterImageTools() {
	// List images tool
	listImagesTool := mcp.NewTool(
		"list_images",
		mcp.WithDescription("List Images in the Harvester cluster"),
		mcp.WithString("namespace",
			mcp.Description("The namespace to list images from (optional, defaults to all namespaces)"),
		),
	)
	s.mcpServer.AddTool(listImagesTool, func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
		namespace, _ := req.Params.Arguments["namespace"].(string)

		// Use the unified resource handler
		gvr := kubernetes.ResourceTypeToGVR[kubernetes.ResourceTypeImages]
		list, err := s.resourceHandler.ListResources(ctx, gvr, namespace)
		if err != nil {
			return mcp.NewToolResultError(fmt.Sprintf("Failed to list images: %v", err)), nil
		}

		// Format the list using the resource formatter
		formatted := s.resourceHandler.FormatResourceList(list, gvr)
		return mcp.NewToolResultText(formatted), nil
	})
}

// registerHarvesterVolumeTools registers Harvester Volume-related tools.
func (s *HarvesterMCPServer) registerHarvesterVolumeTools() {
	// List volumes tool
	listVolumesTool := mcp.NewTool(
		"list_volumes",
		mcp.WithDescription("List Volumes in the Harvester cluster"),
		mcp.WithString("namespace",
			mcp.Description("The namespace to list volumes from (optional, defaults to all namespaces)"),
		),
	)
	s.mcpServer.AddTool(listVolumesTool, func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
		namespace, _ := req.Params.Arguments["namespace"].(string)

		// Use the unified resource handler
		gvr := kubernetes.ResourceTypeToGVR[kubernetes.ResourceTypeVolumes]
		list, err := s.resourceHandler.ListResources(ctx, gvr, namespace)
		if err != nil {
			return mcp.NewToolResultError(fmt.Sprintf("Failed to list volumes: %v", err)), nil
		}

		// Format the list using the resource formatter
		formatted := s.resourceHandler.FormatResourceList(list, gvr)
		return mcp.NewToolResultText(formatted), nil
	})
}

// registerHarvesterNetworkTools registers Harvester Network-related tools.
func (s *HarvesterMCPServer) registerHarvesterNetworkTools() {
	// List networks tool
	listNetworksTool := mcp.NewTool(
		"list_networks",
		mcp.WithDescription("List Networks in the Harvester cluster"),
		mcp.WithString("namespace",
			mcp.Description("The namespace to list networks from (optional, defaults to all namespaces)"),
		),
	)
	s.mcpServer.AddTool(listNetworksTool, func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
		namespace, _ := req.Params.Arguments["namespace"].(string)

		// Use the unified resource handler
		gvr := kubernetes.ResourceTypeToGVR[kubernetes.ResourceTypeNetworks]
		list, err := s.resourceHandler.ListResources(ctx, gvr, namespace)
		if err != nil {
			return mcp.NewToolResultError(fmt.Sprintf("Failed to list networks: %v", err)), nil
		}

		// Format the list using the resource formatter
		formatted := s.resourceHandler.FormatResourceList(list, gvr)
		return mcp.NewToolResultText(formatted), nil
	})
}

```

--------------------------------------------------------------------------------
/pkg/kubernetes/core_formatters.go:
--------------------------------------------------------------------------------

```go
package kubernetes

import (
	"fmt"
	"strings"
	"time"

	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

// PodFormatter handles formatting for Pod resources
type PodFormatter struct{}

func (f *PodFormatter) FormatResource(res *unstructured.Unstructured) string {
	var sb strings.Builder
	sb.WriteString(fmt.Sprintf("Pod: %s\n", res.GetName()))
	sb.WriteString(fmt.Sprintf("Namespace: %s\n", res.GetNamespace()))

	// Status
	status := getNestedString(res.Object, "status", "phase")
	reason := getNestedString(res.Object, "status", "reason")
	if reason != "" {
		status = reason
	}
	sb.WriteString(fmt.Sprintf("Status: %s\n", status))

	// Node
	nodeName := getNestedString(res.Object, "spec", "nodeName")
	if nodeName != "" {
		sb.WriteString(fmt.Sprintf("Node: %s\n", nodeName))
	}

	// IP addresses
	podIP := getNestedString(res.Object, "status", "podIP")
	if podIP != "" {
		sb.WriteString(fmt.Sprintf("Pod IP: %s\n", podIP))
	}

	// Creation time
	creationTime := res.GetCreationTimestamp().Format(time.RFC3339)
	sb.WriteString(fmt.Sprintf("Created: %s\n", creationTime))

	// QoS Class
	qosClass := getNestedString(res.Object, "status", "qosClass")
	sb.WriteString(fmt.Sprintf("QoS Class: %s\n", qosClass))

	// Labels
	if labels := res.GetLabels(); len(labels) > 0 {
		sb.WriteString("\nLabels:\n")
		for key, value := range labels {
			sb.WriteString(fmt.Sprintf("  %s: %s\n", key, value))
		}
	}

	// Containers
	containers, _, _ := unstructured.NestedSlice(res.Object, "spec", "containers")
	if len(containers) > 0 {
		sb.WriteString("\nContainers:\n")
		for i, containerObj := range containers {
			container, ok := containerObj.(map[string]interface{})
			if !ok {
				continue
			}

			name, _, _ := unstructured.NestedString(container, "name")
			image, _, _ := unstructured.NestedString(container, "image")

			sb.WriteString(fmt.Sprintf("  %d. %s\n", i+1, name))
			sb.WriteString(fmt.Sprintf("     Image: %s\n", image))

			// Container resources
			resources, found, _ := unstructured.NestedMap(container, "resources")
			if found {
				sb.WriteString("     Resources:\n")
				limits, limitsFound, _ := unstructured.NestedMap(resources, "limits")
				if limitsFound {
					for resource, value := range limits {
						sb.WriteString(fmt.Sprintf("       Limits %s: %v\n", resource, value))
					}
				}

				requests, requestsFound, _ := unstructured.NestedMap(resources, "requests")
				if requestsFound {
					for resource, value := range requests {
						sb.WriteString(fmt.Sprintf("       Requests %s: %v\n", resource, value))
					}
				}
			}

			sb.WriteString("\n")
		}
	}

	return sb.String()
}

func (f *PodFormatter) FormatResourceList(list *unstructured.UnstructuredList) string {
	if len(list.Items) == 0 {
		return "No pods found in the specified namespace(s)."
	}

	var sb strings.Builder
	sb.WriteString(fmt.Sprintf("Found %d pod(s):\n\n", len(list.Items)))

	// Group pods by namespace
	podsByNamespace := make(map[string][]unstructured.Unstructured)
	for _, item := range list.Items {
		namespace := item.GetNamespace()
		podsByNamespace[namespace] = append(podsByNamespace[namespace], item)
	}

	// Print pods grouped by namespace
	for namespace, pods := range podsByNamespace {
		sb.WriteString(fmt.Sprintf("Namespace: %s (%d pods)\n", namespace, len(pods)))

		for _, pod := range pods {
			// Get pod status
			status := getNestedString(pod.Object, "status", "phase")
			reason := getNestedString(pod.Object, "status", "reason")
			if reason != "" {
				status = reason
			}

			// Add ready container count
			ready := 0
			containerStatuses, _, _ := unstructured.NestedSlice(pod.Object, "status", "containerStatuses")
			for _, csObj := range containerStatuses {
				cs, ok := csObj.(map[string]interface{})
				if ok {
					isReady, found, _ := unstructured.NestedBool(cs, "ready")
					if found && isReady {
						ready++
					}
				}
			}

			containers, _, _ := unstructured.NestedSlice(pod.Object, "spec", "containers")

			// Basic pod info
			sb.WriteString(fmt.Sprintf("  • %s\n", pod.GetName()))
			sb.WriteString(fmt.Sprintf("    Status: %s\n", status))
			sb.WriteString(fmt.Sprintf("    Ready: %d/%d containers\n", ready, len(containers)))

			// Add node name if scheduled
			nodeName := getNestedString(pod.Object, "spec", "nodeName")
			if nodeName != "" {
				sb.WriteString(fmt.Sprintf("    Node: %s\n", nodeName))
			}

			// Add IP if assigned
			podIP := getNestedString(pod.Object, "status", "podIP")
			if podIP != "" {
				sb.WriteString(fmt.Sprintf("    IP: %s\n", podIP))
			}

			// Add creation time
			creationTime := pod.GetCreationTimestamp().Format(time.RFC3339)
			sb.WriteString(fmt.Sprintf("    Created: %s\n", creationTime))

			// Add restart count
			totalRestarts := 0
			for _, csObj := range containerStatuses {
				cs, ok := csObj.(map[string]interface{})
				if ok {
					restarts, found, _ := unstructured.NestedInt64(cs, "restartCount")
					if found {
						totalRestarts += int(restarts)
					}
				}
			}
			sb.WriteString(fmt.Sprintf("    Restarts: %d\n", totalRestarts))

			sb.WriteString("\n")
		}

		sb.WriteString("\n")
	}

	return sb.String()
}

// ServiceFormatter handles formatting for Service resources
type ServiceFormatter struct{}

func (f *ServiceFormatter) FormatResource(res *unstructured.Unstructured) string {
	var sb strings.Builder
	sb.WriteString(fmt.Sprintf("Service: %s\n", res.GetName()))
	sb.WriteString(fmt.Sprintf("Namespace: %s\n", res.GetNamespace()))

	// Type
	svcType := getNestedString(res.Object, "spec", "type")
	sb.WriteString(fmt.Sprintf("Type: %s\n", svcType))

	// ClusterIP
	clusterIP := getNestedString(res.Object, "spec", "clusterIP")
	sb.WriteString(fmt.Sprintf("Cluster IP: %s\n", clusterIP))

	// External IPs
	externalIPs, _, _ := unstructured.NestedStringSlice(res.Object, "spec", "externalIPs")
	if len(externalIPs) > 0 {
		sb.WriteString("External IPs:\n")
		for _, ip := range externalIPs {
			sb.WriteString(fmt.Sprintf("  %s\n", ip))
		}
	}

	// Selectors
	selector, selectorFound, _ := unstructured.NestedMap(res.Object, "spec", "selector")
	if selectorFound && len(selector) > 0 {
		sb.WriteString("\nSelector:\n")
		for key, value := range selector {
			sb.WriteString(fmt.Sprintf("  %s: %v\n", key, value))
		}
	}

	// Ports
	ports, _, _ := unstructured.NestedSlice(res.Object, "spec", "ports")
	if len(ports) > 0 {
		sb.WriteString("\nPorts:\n")
		for _, portObj := range ports {
			port, ok := portObj.(map[string]interface{})
			if !ok {
				continue
			}

			portNumber, _, _ := unstructured.NestedInt64(port, "port")
			targetPort, _, _ := unstructured.NestedFieldNoCopy(port, "targetPort")
			protocol, _, _ := unstructured.NestedString(port, "protocol")
			name, _, _ := unstructured.NestedString(port, "name")

			if name != "" {
				sb.WriteString(fmt.Sprintf("  %s:\n", name))
				sb.WriteString(fmt.Sprintf("    Port: %d\n", portNumber))
				sb.WriteString(fmt.Sprintf("    Target Port: %v\n", targetPort))
				sb.WriteString(fmt.Sprintf("    Protocol: %s\n", protocol))
			} else {
				sb.WriteString(fmt.Sprintf("  Port: %d\n", portNumber))
				sb.WriteString(fmt.Sprintf("  Target Port: %v\n", targetPort))
				sb.WriteString(fmt.Sprintf("  Protocol: %s\n", protocol))
			}
			sb.WriteString("\n")
		}
	}

	// Creation time
	creationTime := res.GetCreationTimestamp().Format(time.RFC3339)
	sb.WriteString(fmt.Sprintf("Created: %s\n", creationTime))

	return sb.String()
}

func (f *ServiceFormatter) FormatResourceList(list *unstructured.UnstructuredList) string {
	if len(list.Items) == 0 {
		return "No services found in the specified namespace(s)."
	}

	var sb strings.Builder
	sb.WriteString(fmt.Sprintf("Found %d service(s):\n\n", len(list.Items)))

	// Group services by namespace
	servicesByNamespace := make(map[string][]unstructured.Unstructured)
	for _, item := range list.Items {
		namespace := item.GetNamespace()
		servicesByNamespace[namespace] = append(servicesByNamespace[namespace], item)
	}

	// Print services grouped by namespace
	for namespace, services := range servicesByNamespace {
		sb.WriteString(fmt.Sprintf("Namespace: %s (%d services)\n", namespace, len(services)))

		for _, svc := range services {
			// Type
			svcType := getNestedString(svc.Object, "spec", "type")

			// ClusterIP
			clusterIP := getNestedString(svc.Object, "spec", "clusterIP")

			// Ports
			ports, _, _ := unstructured.NestedSlice(svc.Object, "spec", "ports")

			// Basic service info
			sb.WriteString(fmt.Sprintf("  • %s\n", svc.GetName()))
			sb.WriteString(fmt.Sprintf("    Type: %s\n", svcType))
			sb.WriteString(fmt.Sprintf("    Cluster IP: %s\n", clusterIP))

			if len(ports) > 0 {
				sb.WriteString("    Ports:\n")
				for _, portObj := range ports {
					port, ok := portObj.(map[string]interface{})
					if !ok {
						continue
					}

					portNumber, _, _ := unstructured.NestedInt64(port, "port")
					targetPort, _, _ := unstructured.NestedFieldNoCopy(port, "targetPort")
					protocol, _, _ := unstructured.NestedString(port, "protocol")
					name, _, _ := unstructured.NestedString(port, "name")

					portInfo := fmt.Sprintf("%d", portNumber)
					if name != "" {
						portInfo = fmt.Sprintf("%s:%d", name, portNumber)
					}

					sb.WriteString(fmt.Sprintf("      %s → %v/%s\n", portInfo, targetPort, protocol))
				}
			}

			// External IP if available
			externalIPs, _, _ := unstructured.NestedStringSlice(svc.Object, "spec", "externalIPs")
			if len(externalIPs) > 0 {
				sb.WriteString("    External IPs:\n")
				for _, ip := range externalIPs {
					sb.WriteString(fmt.Sprintf("      %s\n", ip))
				}
			}

			// Creation time
			creationTime := svc.GetCreationTimestamp().Format(time.RFC3339)
			sb.WriteString(fmt.Sprintf("    Created: %s\n", creationTime))

			sb.WriteString("\n")
		}

		sb.WriteString("\n")
	}

	return sb.String()
}

// NamespaceFormatter handles formatting for Namespace resources
type NamespaceFormatter struct{}

func (f *NamespaceFormatter) FormatResource(res *unstructured.Unstructured) string {
	var sb strings.Builder
	sb.WriteString(fmt.Sprintf("Namespace: %s\n", res.GetName()))

	// Status
	status := getNestedString(res.Object, "status", "phase")
	sb.WriteString(fmt.Sprintf("Status: %s\n", status))

	// Creation time
	creationTime := res.GetCreationTimestamp().Format(time.RFC3339)
	sb.WriteString(fmt.Sprintf("Created: %s\n", creationTime))

	// Labels
	if labels := res.GetLabels(); len(labels) > 0 {
		sb.WriteString("\nLabels:\n")
		for key, value := range labels {
			sb.WriteString(fmt.Sprintf("  %s: %s\n", key, value))
		}
	}

	// Annotations
	if annotations := res.GetAnnotations(); len(annotations) > 0 {
		sb.WriteString("\nAnnotations:\n")
		for key, value := range annotations {
			sb.WriteString(fmt.Sprintf("  %s: %s\n", key, value))
		}
	}

	return sb.String()
}

func (f *NamespaceFormatter) FormatResourceList(list *unstructured.UnstructuredList) string {
	if len(list.Items) == 0 {
		return "No namespaces found."
	}

	var sb strings.Builder
	sb.WriteString(fmt.Sprintf("Found %d namespace(s):\n\n", len(list.Items)))

	for _, ns := range list.Items {
		// Get status
		status := getNestedString(ns.Object, "status", "phase")

		// Basic namespace info
		sb.WriteString(fmt.Sprintf("• %s\n", ns.GetName()))
		sb.WriteString(fmt.Sprintf("  Status: %s\n", status))

		// Creation time
		creationTime := ns.GetCreationTimestamp().Format(time.RFC3339)
		sb.WriteString(fmt.Sprintf("  Created: %s\n", creationTime))

		sb.WriteString("\n")
	}

	return sb.String()
}

// NodeFormatter handles formatting for Node resources
type NodeFormatter struct{}

func (f *NodeFormatter) FormatResource(res *unstructured.Unstructured) string {
	var sb strings.Builder
	sb.WriteString(fmt.Sprintf("Node: %s\n", res.GetName()))

	// Node status
	status := "Unknown"
	conditions, _, _ := unstructured.NestedSlice(res.Object, "status", "conditions")
	for _, condObj := range conditions {
		cond, ok := condObj.(map[string]interface{})
		if !ok {
			continue
		}

		typeName, typeFound, _ := unstructured.NestedString(cond, "type")
		statusVal, statusFound, _ := unstructured.NestedString(cond, "status")

		if typeFound && statusFound && typeName == "Ready" {
			if statusVal == "True" {
				status = "Ready"
			} else {
				status = "NotReady"
			}
			break
		}
	}
	sb.WriteString(fmt.Sprintf("Status: %s\n", status))

	// Detailed conditions
	sb.WriteString("\nConditions:\n")
	for _, condObj := range conditions {
		cond, ok := condObj.(map[string]interface{})
		if !ok {
			continue
		}

		typeName, _, _ := unstructured.NestedString(cond, "type")
		statusVal, _, _ := unstructured.NestedString(cond, "status")
		message, _, _ := unstructured.NestedString(cond, "message")

		sb.WriteString(fmt.Sprintf("  %s: %s\n", typeName, statusVal))
		if message != "" {
			sb.WriteString(fmt.Sprintf("    Message: %s\n", message))
		}
	}

	// Addresses
	addresses, _, _ := unstructured.NestedSlice(res.Object, "status", "addresses")
	if len(addresses) > 0 {
		sb.WriteString("\nAddresses:\n")
		for _, addrObj := range addresses {
			addr, ok := addrObj.(map[string]interface{})
			if !ok {
				continue
			}

			addrType, typeFound, _ := unstructured.NestedString(addr, "type")
			addrVal, valFound, _ := unstructured.NestedString(addr, "address")

			if typeFound && valFound {
				sb.WriteString(fmt.Sprintf("  %s: %s\n", addrType, addrVal))
			}
		}
	}

	// Node info
	nodeInfo, _, _ := unstructured.NestedMap(res.Object, "status", "nodeInfo")
	if len(nodeInfo) > 0 {
		sb.WriteString("\nNode Info:\n")
		for key, value := range nodeInfo {
			sb.WriteString(fmt.Sprintf("  %s: %v\n", key, value))
		}
	}

	// Resources
	allocatable, _, _ := unstructured.NestedMap(res.Object, "status", "allocatable")
	capacity, _, _ := unstructured.NestedMap(res.Object, "status", "capacity")

	if len(allocatable) > 0 {
		sb.WriteString("\nAllocatable Resources:\n")
		for key, value := range allocatable {
			sb.WriteString(fmt.Sprintf("  %s: %v\n", key, value))
		}
	}

	if len(capacity) > 0 {
		sb.WriteString("\nCapacity:\n")
		for key, value := range capacity {
			sb.WriteString(fmt.Sprintf("  %s: %v\n", key, value))
		}
	}

	// Creation time
	creationTime := res.GetCreationTimestamp().Format(time.RFC3339)
	sb.WriteString(fmt.Sprintf("\nCreated: %s\n", creationTime))

	return sb.String()
}

func (f *NodeFormatter) FormatResourceList(list *unstructured.UnstructuredList) string {
	if len(list.Items) == 0 {
		return "No nodes found."
	}

	var sb strings.Builder
	sb.WriteString(fmt.Sprintf("Found %d node(s):\n\n", len(list.Items)))

	for _, node := range list.Items {
		// Get node status
		status := "Ready"
		conditions, _, _ := unstructured.NestedSlice(node.Object, "status", "conditions")
		for _, condObj := range conditions {
			cond, ok := condObj.(map[string]interface{})
			if !ok {
				continue
			}

			typeName, typeFound, _ := unstructured.NestedString(cond, "type")
			status, statusFound, _ := unstructured.NestedString(cond, "status")

			if typeFound && statusFound && typeName == "Ready" {
				if status == "True" {
					status = "Ready"
				} else {
					status = "NotReady"
				}
				break
			}
		}

		// Get addresses
		var internalIP, externalIP, hostname string
		addresses, _, _ := unstructured.NestedSlice(node.Object, "status", "addresses")
		for _, addrObj := range addresses {
			addr, ok := addrObj.(map[string]interface{})
			if !ok {
				continue
			}

			addrType, typeFound, _ := unstructured.NestedString(addr, "type")
			addrVal, valFound, _ := unstructured.NestedString(addr, "address")

			if typeFound && valFound {
				switch addrType {
				case "InternalIP":
					internalIP = addrVal
				case "ExternalIP":
					externalIP = addrVal
				case "Hostname":
					hostname = addrVal
				}
			}
		}

		// Get kubelet version
		kubeletVersion := getNestedString(node.Object, "status", "nodeInfo", "kubeletVersion")

		// Get allocatable resources
		allocatable, _, _ := unstructured.NestedMap(node.Object, "status", "allocatable")
		cpu := ""
		memory := ""
		if allocatable != nil {
			if cpuVal, ok := allocatable["cpu"]; ok {
				cpu = fmt.Sprintf("%v", cpuVal)
			}
			if memVal, ok := allocatable["memory"]; ok {
				memory = fmt.Sprintf("%v", memVal)
			}
		}

		// Basic node info
		sb.WriteString(fmt.Sprintf("• %s\n", node.GetName()))
		sb.WriteString(fmt.Sprintf("  Status: %s\n", status))

		if internalIP != "" {
			sb.WriteString(fmt.Sprintf("  Internal IP: %s\n", internalIP))
		}

		if externalIP != "" {
			sb.WriteString(fmt.Sprintf("  External IP: %s\n", externalIP))
		}

		if hostname != "" && hostname != node.GetName() {
			sb.WriteString(fmt.Sprintf("  Hostname: %s\n", hostname))
		}

		if kubeletVersion != "" {
			sb.WriteString(fmt.Sprintf("  Kubelet Version: %s\n", kubeletVersion))
		}

		if cpu != "" || memory != "" {
			sb.WriteString("  Allocatable:\n")
			if cpu != "" {
				sb.WriteString(fmt.Sprintf("    CPU: %s\n", cpu))
			}
			if memory != "" {
				sb.WriteString(fmt.Sprintf("    Memory: %s\n", memory))
			}
		}

		// Creation time
		creationTime := node.GetCreationTimestamp().Format(time.RFC3339)
		sb.WriteString(fmt.Sprintf("  Created: %s\n", creationTime))

		sb.WriteString("\n")
	}

	return sb.String()
}

// DeploymentFormatter handles formatting for Deployment resources
type DeploymentFormatter struct{}

func (f *DeploymentFormatter) FormatResource(res *unstructured.Unstructured) string {
	var sb strings.Builder
	sb.WriteString(fmt.Sprintf("Deployment: %s\n", res.GetName()))
	sb.WriteString(fmt.Sprintf("Namespace: %s\n", res.GetNamespace()))

	// Replicas info
	replicas := getNestedInt64(res.Object, "spec", "replicas")
	available := getNestedInt64(res.Object, "status", "availableReplicas")
	ready := getNestedInt64(res.Object, "status", "readyReplicas")
	updated := getNestedInt64(res.Object, "status", "updatedReplicas")

	sb.WriteString(fmt.Sprintf("Replicas: %d desired | %d updated | %d total | %d available | %d ready\n",
		replicas, updated, replicas, available, ready))

	// Strategy
	strategy := getNestedString(res.Object, "spec", "strategy", "type")
	sb.WriteString(fmt.Sprintf("Strategy: %s\n", strategy))

	// Selector
	selector, selectorFound, _ := unstructured.NestedMap(res.Object, "spec", "selector", "matchLabels")
	if selectorFound && len(selector) > 0 {
		sb.WriteString("\nSelector:\n")
		for key, value := range selector {
			sb.WriteString(fmt.Sprintf("  %s: %v\n", key, value))
		}
	}

	// Containers
	containers, _, _ := unstructured.NestedSlice(res.Object, "spec", "template", "spec", "containers")
	if len(containers) > 0 {
		sb.WriteString("\nContainers:\n")
		for i, containerObj := range containers {
			container, ok := containerObj.(map[string]interface{})
			if !ok {
				continue
			}

			name, _, _ := unstructured.NestedString(container, "name")
			image, _, _ := unstructured.NestedString(container, "image")

			sb.WriteString(fmt.Sprintf("  %d. %s\n", i+1, name))
			sb.WriteString(fmt.Sprintf("     Image: %s\n", image))

			// Container resources
			resources, found, _ := unstructured.NestedMap(container, "resources")
			if found {
				sb.WriteString("     Resources:\n")
				limits, limitsFound, _ := unstructured.NestedMap(resources, "limits")
				if limitsFound {
					for resource, value := range limits {
						sb.WriteString(fmt.Sprintf("       Limits %s: %v\n", resource, value))
					}
				}

				requests, requestsFound, _ := unstructured.NestedMap(resources, "requests")
				if requestsFound {
					for resource, value := range requests {
						sb.WriteString(fmt.Sprintf("       Requests %s: %v\n", resource, value))
					}
				}
			}

			sb.WriteString("\n")
		}
	}

	// Conditions
	conditions, _, _ := unstructured.NestedSlice(res.Object, "status", "conditions")
	if len(conditions) > 0 {
		sb.WriteString("\nConditions:\n")
		for _, condObj := range conditions {
			cond, ok := condObj.(map[string]interface{})
			if !ok {
				continue
			}

			typeName, _, _ := unstructured.NestedString(cond, "type")
			statusVal, _, _ := unstructured.NestedString(cond, "status")
			reason, _, _ := unstructured.NestedString(cond, "reason")
			message, _, _ := unstructured.NestedString(cond, "message")

			sb.WriteString(fmt.Sprintf("  %s: %s\n", typeName, statusVal))
			if reason != "" {
				sb.WriteString(fmt.Sprintf("    Reason: %s\n", reason))
			}
			if message != "" {
				sb.WriteString(fmt.Sprintf("    Message: %s\n", message))
			}
		}
	}

	// Creation time
	creationTime := res.GetCreationTimestamp().Format(time.RFC3339)
	sb.WriteString(fmt.Sprintf("\nCreated: %s\n", creationTime))

	return sb.String()
}

func (f *DeploymentFormatter) FormatResourceList(list *unstructured.UnstructuredList) string {
	if len(list.Items) == 0 {
		return "No deployments found in the specified namespace(s)."
	}

	var sb strings.Builder
	sb.WriteString(fmt.Sprintf("Found %d deployment(s):\n\n", len(list.Items)))

	// Group deployments by namespace
	deploymentsByNamespace := make(map[string][]unstructured.Unstructured)
	for _, item := range list.Items {
		namespace := item.GetNamespace()
		deploymentsByNamespace[namespace] = append(deploymentsByNamespace[namespace], item)
	}

	// Print deployments grouped by namespace
	for namespace, deployments := range deploymentsByNamespace {
		sb.WriteString(fmt.Sprintf("Namespace: %s (%d deployments)\n", namespace, len(deployments)))

		for _, deployment := range deployments {
			// Get deployment status
			available := getNestedInt64(deployment.Object, "status", "availableReplicas")
			replicas := getNestedInt64(deployment.Object, "status", "replicas")
			updatedReplicas := getNestedInt64(deployment.Object, "status", "updatedReplicas")

			// Basic deployment info
			sb.WriteString(fmt.Sprintf("  • %s\n", deployment.GetName()))
			sb.WriteString(fmt.Sprintf("    Replicas: %d available / %d total / %d updated\n", available, replicas, updatedReplicas))

			// Add image info if available
			containers, _, _ := unstructured.NestedSlice(deployment.Object, "spec", "template", "spec", "containers")
			if len(containers) > 0 {
				sb.WriteString("    Images:\n")
				for _, containerObj := range containers {
					container, ok := containerObj.(map[string]interface{})
					if !ok {
						continue
					}

					name, _, _ := unstructured.NestedString(container, "name")
					image, _, _ := unstructured.NestedString(container, "image")

					sb.WriteString(fmt.Sprintf("      %s: %s\n", name, image))
				}
			}

			// Status conditions
			conditions, _, _ := unstructured.NestedSlice(deployment.Object, "status", "conditions")
			if len(conditions) > 0 {
				sb.WriteString("    Conditions:\n")
				for _, condObj := range conditions {
					cond, ok := condObj.(map[string]interface{})
					if !ok {
						continue
					}

					typeName, _, _ := unstructured.NestedString(cond, "type")
					statusVal, _, _ := unstructured.NestedString(cond, "status")

					sb.WriteString(fmt.Sprintf("      %s: %s\n", typeName, statusVal))
				}
			}

			// Creation time
			creationTime := deployment.GetCreationTimestamp().Format(time.RFC3339)
			sb.WriteString(fmt.Sprintf("    Created: %s\n", creationTime))

			sb.WriteString("\n")
		}

		sb.WriteString("\n")
	}

	return sb.String()
}

```