#
tokens: 35225/50000 9/82 files (page 3/5)
lines: off (toggle) GitHub
raw markdown copy
This is page 3 of 5. Use http://codebase.md/blankcut/kubernetes-mcp-server?page={x} to view the full context.

# Directory Structure

```
├── .gitignore
├── docs
│   ├── .astro
│   │   ├── collections
│   │   │   └── docs.schema.json
│   │   ├── content-assets.mjs
│   │   ├── content-modules.mjs
│   │   ├── content.d.ts
│   │   ├── data-store.json
│   │   ├── settings.json
│   │   └── types.d.ts
│   ├── .gitignore
│   ├── astro.config.mjs
│   ├── package-lock.json
│   ├── package.json
│   ├── public
│   │   └── images
│   │       └── logo.svg
│   ├── README.md
│   ├── src
│   │   ├── components
│   │   │   ├── CodeBlock.astro
│   │   │   ├── DocSidebar.astro
│   │   │   ├── Footer.astro
│   │   │   ├── Header.astro
│   │   │   ├── HeadSEO.astro
│   │   │   ├── Search.astro
│   │   │   ├── Sidebar.astro
│   │   │   └── TableOfContents.astro
│   │   ├── content
│   │   │   ├── config.ts
│   │   │   └── docs
│   │   │       ├── api-overview.md
│   │   │       ├── configuration.md
│   │   │       ├── installation.md
│   │   │       ├── introduction.md
│   │   │       ├── model-context-protocol.md
│   │   │       ├── quick-start.md
│   │   │       └── troubleshooting-resources.md
│   │   ├── env.d.ts
│   │   ├── layouts
│   │   │   ├── BaseLayout.astro
│   │   │   └── DocLayout.astro
│   │   ├── pages
│   │   │   ├── [...slug].astro
│   │   │   ├── 404.astro
│   │   │   ├── docs
│   │   │   │   └── index.astro
│   │   │   ├── docs-test.astro
│   │   │   ├── examples
│   │   │   │   └── index.astro
│   │   │   └── index.astro
│   │   └── styles
│   │       └── global.css
│   ├── tailwind.config.cjs
│   └── tsconfig.json
├── go.mod
├── kubernetes-claude-mcp
│   ├── .gitignore
│   ├── cmd
│   │   └── server
│   │       └── main.go
│   ├── docker-compose.yml
│   ├── Dockerfile
│   ├── go.mod
│   ├── go.sum
│   ├── internal
│   │   ├── api
│   │   │   ├── namespace_routes.go
│   │   │   ├── routes.go
│   │   │   └── server.go
│   │   ├── argocd
│   │   │   ├── applications.go
│   │   │   ├── client.go
│   │   │   └── history.go
│   │   ├── auth
│   │   │   ├── credentials.go
│   │   │   ├── secrets.go
│   │   │   └── vault.go
│   │   ├── claude
│   │   │   ├── client.go
│   │   │   └── protocol.go
│   │   ├── correlator
│   │   │   ├── gitops.go
│   │   │   ├── helm_correlator.go
│   │   │   └── troubleshoot.go
│   │   ├── gitlab
│   │   │   ├── client.go
│   │   │   ├── mergerequests.go
│   │   │   ├── pipelines.go
│   │   │   └── repositories.go
│   │   ├── helm
│   │   │   └── parser.go
│   │   ├── k8s
│   │   │   ├── client.go
│   │   │   ├── enhanced_client.go
│   │   │   ├── events.go
│   │   │   ├── resource_mapper.go
│   │   │   └── resources.go
│   │   ├── mcp
│   │   │   ├── context.go
│   │   │   ├── namespace_analyzer.go
│   │   │   ├── prompt.go
│   │   │   └── protocol.go
│   │   └── models
│   │       ├── argocd.go
│   │       ├── context.go
│   │       ├── gitlab.go
│   │       └── kubernetes.go
│   └── pkg
│       ├── config
│       │   └── config.go
│       ├── logging
│       │   └── logging.go
│       └── utils
│           ├── serialization.go
│           └── truncation.go
├── LICENSE
└── README.md
```

# Files

--------------------------------------------------------------------------------
/docs/src/content/docs/installation.md:
--------------------------------------------------------------------------------

```markdown
---
title: Installation Guide
description: Comprehensive guide for installing and configuring the Kubernetes Claude MCP server in various environments.
date: 2025-03-01
order: 3
tags: ['installation', 'deployment']
---

# Installation Guide

This guide provides detailed instructions for installing Kubernetes Claude MCP in different environments. Choose the method that best suits your needs.

## Prerequisites

Before installing Kubernetes Claude MCP, ensure you have:

- Access to a Kubernetes cluster (v1.19+)
- kubectl configured to access your cluster
- Claude API key from Anthropic
- Optional: ArgoCD instance (for GitOps integration)
- Optional: GitLab access (for commit analysis)

## Installation Methods

There are several ways to install Kubernetes Claude MCP:

1. [Docker Compose](#docker-compose) (for development/testing)
2. [Kubernetes Deployment](#kubernetes-deployment) (recommended for production)
3. [Helm Chart](#helm-chart) (easiest for Kubernetes)
4. [Manual Binary](#manual-binary) (for custom environments)

## Docker Compose

Docker Compose is ideal for local development and testing.

### Step 1: Clone the Repository

```bash
git clone https://github.com/blankcut/kubernetes-mcp-server.git
cd kubernetes-mcp-server
```

### Step 2: Configure Environment Variables

Create a `.env` file with your credentials:

```bash
CLAUDE_API_KEY=your_claude_api_key
ARGOCD_USERNAME=your_argocd_username
ARGOCD_PASSWORD=your_argocd_password
GITLAB_AUTH_TOKEN=your_gitlab_token
API_KEY=your_api_key_for_server_access
```

### Step 3: Configure the Server

Create or modify `config.yaml`:

```yaml
server:
  address: ":8080"
  readTimeout: 30
  writeTimeout: 60
  auth:
    apiKey: "${API_KEY}"

kubernetes:
  kubeconfig: ""
  inCluster: false
  defaultContext: ""
  defaultNamespace: "default"

argocd:
  url: "${ARGOCD_URL}"
  authToken: "${ARGOCD_AUTH_TOKEN}"
  username: "${ARGOCD_USERNAME}"
  password: "${ARGOCD_PASSWORD}"
  insecure: true

gitlab:
  url: "${GITLAB_URL}"
  authToken: "${GITLAB_AUTH_TOKEN}"
  apiVersion: "v4"
  projectPath: "${PROJECT_PATH}"

claude:
  apiKey: "${CLAUDE_API_KEY}"
  baseURL: "https://api.anthropic.com"
  modelID: "claude-3-haiku-20240307"
  maxTokens: 4096
  temperature: 0.7
```

### Step 4: Start the Service

```bash
docker-compose up -d
```

The server will be available at http://localhost:8080.

## Kubernetes Deployment

For production environments, deploying to Kubernetes is recommended.

### Step 1: Create a Namespace

```bash
kubectl create namespace mcp-system
```

### Step 2: Create Secrets

```bash
kubectl create secret generic mcp-secrets \
  --namespace mcp-system \
  --from-literal=claude-api-key=your_claude_api_key \
  --from-literal=argocd-username=your_argocd_username \
  --from-literal=argocd-password=your_argocd_password \
  --from-literal=gitlab-token=your_gitlab_token \
  --from-literal=api-key=your_api_key_for_server_access
```

### Step 3: Create ConfigMap

```bash
kubectl create configmap mcp-config \
  --namespace mcp-system \
  --from-file=config.yaml
```

### Step 4: Apply Deployment Manifest

Create a file named `deployment.yaml`:

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kubernetes-mcp-server
  namespace: mcp-system
  labels:
    app: kubernetes-mcp-server
spec:
  replicas: 1
  selector:
    matchLabels:
      app: kubernetes-mcp-server
  template:
    metadata:
      labels:
        app: kubernetes-mcp-server
    spec:
      serviceAccountName: mcp-service-account
      containers:
      - name: server
        image: blankcut/kubernetes-mcp-server:latest
        imagePullPolicy: Always
        ports:
        - containerPort: 8080
        env:
        - name: CLAUDE_API_KEY
          valueFrom:
            secretKeyRef:
              name: mcp-secrets
              key: claude-api-key
        - name: ARGOCD_USERNAME
          valueFrom:
            secretKeyRef:
              name: mcp-secrets
              key: argocd-username
              optional: true
        - name: ARGOCD_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mcp-secrets
              key: argocd-password
              optional: true
        - name: GITLAB_AUTH_TOKEN
          valueFrom:
            secretKeyRef:
              name: mcp-secrets
              key: gitlab-token
              optional: true
        - name: API_KEY
          valueFrom:
            secretKeyRef:
              name: mcp-secrets
              key: api-key
        volumeMounts:
        - name: config
          mountPath: /app/config.yaml
          subPath: config.yaml
      volumes:
      - name: config
        configMap:
          name: mcp-config
---
apiVersion: v1
kind: Service
metadata:
  name: kubernetes-mcp-server
  namespace: mcp-system
spec:
  selector:
    app: kubernetes-mcp-server
  ports:
  - port: 80
    targetPort: 8080
  type: ClusterIP
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: mcp-service-account
  namespace: mcp-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: mcp-cluster-role
rules:
- apiGroups: [""]
  resources: ["pods", "services", "events", "configmaps", "secrets", "namespaces", "nodes"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
  resources: ["deployments", "statefulsets", "daemonsets", "replicasets"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["batch"]
  resources: ["jobs", "cronjobs"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["networking.k8s.io"]
  resources: ["ingresses"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: mcp-role-binding
subjects:
- kind: ServiceAccount
  name: mcp-service-account
  namespace: mcp-system
roleRef:
  kind: ClusterRole
  name: mcp-cluster-role
  apiGroup: rbac.authorization.k8s.io
```

Apply the configuration:

```bash
kubectl apply -f deployment.yaml
```

### Step 5: Access the Server

Create an Ingress or port-forward to access the server:

```bash
kubectl port-forward -n mcp-system svc/kubernetes-mcp-server 8080:80
```

## Helm Chart

For Kubernetes users, the Helm chart provides the easiest installation method.

### Step 1: Add the Helm Repository

```bash
helm repo add blankcut https://blankcut.github.io/helm-charts
helm repo update
```

### Step 2: Configure Values

Create a `values.yaml` file:

```yaml
image:
  repository: blankcut/kubernetes-mcp-server
  tag: latest

config:
  server:
    address: ":8080"
  kubernetes:
    inCluster: true
    defaultNamespace: "default"
  argocd:
    url: "https://argocd.example.com"
  gitlab:
    url: "https://gitlab.com"
  claude:
    modelID: "claude-3-haiku-20240307"

secrets:
  claude:
    apiKey: "your_claude_api_key"
  argocd:
    username: "your_argocd_username"
    password: "your_argocd_password"
  gitlab:
    authToken: "your_gitlab_token"

service:
  type: ClusterIP

ingress:
  enabled: false
  # Uncomment to enable ingress
  # hosts:
  #   - host: mcp.example.com
  #     paths:
  #       - path: /
  #         pathType: Prefix
```

### Step 3: Install the Chart

```bash
helm install kubernetes-mcp-server blankcut/kubernetes-claude-mcp -f values.yaml -n mcp-system
```

### Step 4: Verify the Installation

```bash
kubectl get pods -n mcp-system
```

## Manual Binary

For environments where Docker or Kubernetes is not available, you can run the binary directly.

### Step 1: Download the Latest Release

Visit the [Releases page](https://github.com/blankcut/kubernetes-mcp-server/releases) and download the appropriate binary for your platform.

### Step 2: Make the Binary Executable

```bash
chmod +x mcp-server
```

### Step 3: Create Configuration File

Create a `config.yaml` file in the same directory:

```yaml
server:
  address: ":8080"
  readTimeout: 30
  writeTimeout: 60
  auth:
    apiKey: "your_api_key_for_server_access"

kubernetes:
  kubeconfig: "/path/to/.kube/config"  # Path to your kubeconfig file
  inCluster: false
  defaultContext: ""
  defaultNamespace: "default"

argocd:
  url: "https://argocd.example.com"
  username: "your_argocd_username"
  password: "your_argocd_password"
  insecure: true

gitlab:
  url: "https://gitlab.com"
  authToken: "your_gitlab_token"
  apiVersion: "v4"
  projectPath: ""

claude:
  apiKey: "your_claude_api_key"
  baseURL: "https://api.anthropic.com"
  modelID: "claude-3-haiku-20240307"
  maxTokens: 4096
  temperature: 0.7
```

### Step 4: Run the Server

```bash
export CLAUDE_API_KEY=your_claude_api_key
export API_KEY=your_api_key_for_server
./mcp-server --config config.yaml
```

## Verifying the Installation

To verify your installation is working correctly:

1. Check the health endpoint:

```bash
curl http://localhost:8080/api/v1/health
```

2. List Kubernetes namespaces:

```bash
curl -H "X-API-Key: your_api_key" http://localhost:8080/api/v1/namespaces
```

3. Test a resource query:

```bash
curl -X POST \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your_api_key" \
  -d '{
    "action": "queryResource",
    "resource": "pod",
    "name": "example-pod",
    "namespace": "default",
    "query": "Is this pod healthy?"
  }' \
  http://localhost:8080/api/v1/mcp/resource
```

## Security Considerations

When deploying Kubernetes Claude MCP, consider the following security best practices:

1. **API Access**: Use a strong API key and restrict access to the server.
2. **Kubernetes Permissions**: Use a service account with the minimum required permissions.
3. **Secrets Management**: Store credentials in Kubernetes Secrets or a secure vault.
4. **Network Isolation**: Consider network policies to limit access to the server.
5. **TLS**: Use TLS to encrypt connections to the server.

For more security recommendations, see the [Security Best Practices](/docs/security-best-practices) guide.

## Troubleshooting

If you encounter issues during installation, check:

1. **Logs**: View server logs for error messages
   ```bash
   # For Docker Compose
   docker-compose logs
   
   # For Kubernetes
   kubectl logs -n mcp-system deployment/kubernetes-mcp-server
   ```

2. **Configuration**: Verify your `config.yaml` has the correct settings
3. **Connectivity**: Ensure the server can connect to Kubernetes, ArgoCD, and GitLab
4. **API Key**: Verify you're using the correct API key in requests

For more troubleshooting tips, see the [Troubleshooting](/docs/troubleshooting-resources) guide.

## Next Steps

After successful installation, continue with:

- [Configuration Guide](/docs/configuration) - Configure the server for your environment
- [API Reference](/docs/api-overview) - Explore the API endpoints
- [Examples](/docs/examples/basic-usage) - See examples of common use cases
```

--------------------------------------------------------------------------------
/kubernetes-claude-mcp/internal/auth/credentials.go:
--------------------------------------------------------------------------------

```go
package auth

import (
	"context"
	"fmt"
	"os"
	"sync"
	"time"

	"github.com/Blankcut/kubernetes-mcp-server/kubernetes-claude-mcp/pkg/config"
	"github.com/Blankcut/kubernetes-mcp-server/kubernetes-claude-mcp/pkg/logging"
)

// ServiceType represents the type of service requiring credentials
type ServiceType string

const (
	ServiceKubernetes ServiceType = "kubernetes"
	ServiceArgoCD     ServiceType = "argocd"
	ServiceGitLab     ServiceType = "gitlab"
	ServiceClaude     ServiceType = "claude"
)

// Credentials stores authentication information for various services
type Credentials struct {
	// API tokens, oauth tokens, etc.
	Token       string
	APIKey      string
	Username    string
	Password    string
	Certificate []byte
	PrivateKey  []byte
	ExpiresAt time.Time
}

// IsExpired checks if the credentials are expired
func (c *Credentials) IsExpired() bool {
	// If no expiration time is set, we'll assume credentials don't expire
	if c.ExpiresAt.IsZero() {
		return false
	}
	
	// Check if current time is past the expiration time
	return time.Now().After(c.ExpiresAt)
}

// CredentialProvider manages credentials for various services
type CredentialProvider struct {
	mu          sync.RWMutex
	credentials map[ServiceType]*Credentials
	config      *config.Config
	logger      *logging.Logger
	secretsManager *SecretsManager
	vaultManager   *VaultManager
}

// NewCredentialProvider creates a new credential provider
func NewCredentialProvider(cfg *config.Config) *CredentialProvider {
	logger := logging.NewLogger().Named("auth")
	
	return &CredentialProvider{
		credentials: make(map[ServiceType]*Credentials),
		config:      cfg,
		logger:      logger,
		secretsManager: NewSecretsManager(logger),
		vaultManager: NewVaultManager(logger),
	}
}

// LoadCredentials loads all service credentials based on configuration
func (p *CredentialProvider) LoadCredentials(ctx context.Context) error {
	// Load credentials for each service type based on config
	if err := p.loadKubernetesCredentials(ctx); err != nil {
		return fmt.Errorf("failed to load Kubernetes credentials: %w", err)
	}

	if err := p.loadArgoCDCredentials(ctx); err != nil {
		return fmt.Errorf("failed to load ArgoCD credentials: %w", err)
	}

	if err := p.loadGitLabCredentials(ctx); err != nil {
		return fmt.Errorf("failed to load GitLab credentials: %w", err)
	}

	if err := p.loadClaudeCredentials(ctx); err != nil {
		return fmt.Errorf("failed to load Claude credentials: %w", err)
	}

	return nil
}

// GetCredentials returns credentials for the specified service
func (p *CredentialProvider) GetCredentials(serviceType ServiceType) (*Credentials, error) {
	p.mu.RLock()
	defer p.mu.RUnlock()

	creds, ok := p.credentials[serviceType]
	if !ok {
		return nil, fmt.Errorf("credentials not found for service: %s", serviceType)
	}
	
	// Check if credentials are expired and need refresh
	if creds.IsExpired() {
		p.mu.RUnlock() // Release read lock
		
		// Acquire write lock for refresh
		p.mu.Lock()
		defer p.mu.Unlock()
		
		// Check again in case another goroutine refreshed while we were waiting
		if creds.IsExpired() {
			p.logger.Info("Refreshing expired credentials", "serviceType", serviceType)
			if err := p.RefreshCredentials(context.Background(), serviceType); err != nil {
				return nil, fmt.Errorf("failed to refresh expired credentials: %w", err)
			}
			creds = p.credentials[serviceType]
		}
	}

	return creds, nil
}

// loadKubernetesCredentials loads Kubernetes authentication credentials
func (p *CredentialProvider) loadKubernetesCredentials(ctx context.Context) error {
	p.mu.Lock()
	defer p.mu.Unlock()

	// For Kubernetes, we primarily rely on kubeconfig or in-cluster config
	// We won't need to store explicit credentials
	p.credentials[ServiceKubernetes] = &Credentials{}
	return nil
}

// loadArgoCDCredentials loads ArgoCD authentication credentials
func (p *CredentialProvider) loadArgoCDCredentials(ctx context.Context) error {
	p.mu.Lock()
	defer p.mu.Unlock()

	// Try to load from secrets manager if available
	if p.secretsManager != nil && p.secretsManager.IsAvailable() {
		creds, err := p.secretsManager.GetCredentials(ctx, "argocd")
		if err == nil && creds != nil {
			p.credentials[ServiceArgoCD] = creds
			p.logger.Info("Loaded ArgoCD credentials from secrets manager")
			return nil
		}
	}
	
	// Try to load from vault if available
	if p.vaultManager != nil && p.vaultManager.IsAvailable() {
		creds, err := p.vaultManager.GetCredentials(ctx, "argocd")
		if err == nil && creds != nil {
			p.credentials[ServiceArgoCD] = creds
			p.logger.Info("Loaded ArgoCD credentials from vault")
			return nil
		}
	}

	// Primary source: Environment variables
	token := os.Getenv("ARGOCD_AUTH_TOKEN")
	if token != "" {
		p.credentials[ServiceArgoCD] = &Credentials{
			Token: token,
		}
		p.logger.Info("Loaded ArgoCD credentials from environment")
		return nil
	}

	// Secondary source: Config file
	if p.config.ArgoCD.AuthToken != "" {
		p.credentials[ServiceArgoCD] = &Credentials{
			Token: p.config.ArgoCD.AuthToken,
		}
		p.logger.Info("Loaded ArgoCD credentials from config file")
		return nil
	}

	// Tertiary source: Username/password...
	username := os.Getenv("ARGOCD_USERNAME")
	password := os.Getenv("ARGOCD_PASSWORD")
	if username != "" && password != "" {
		p.credentials[ServiceArgoCD] = &Credentials{
			Username: username,
			Password: password,
		}
		p.logger.Info("Loaded ArgoCD username/password from environment")
		return nil
	}

	// Final fallback to config
	if p.config.ArgoCD.Username != "" && p.config.ArgoCD.Password != "" {
		p.credentials[ServiceArgoCD] = &Credentials{
			Username: p.config.ArgoCD.Username,
			Password: p.config.ArgoCD.Password,
		}
		p.logger.Info("Loaded ArgoCD username/password from config file")
		return nil
	}

	p.logger.Warn("No ArgoCD credentials found, continuing without them")
	// We don't want to fail if ArgoCD credentials are not found
	// since ArgoCD integration is optional
	p.credentials[ServiceArgoCD] = &Credentials{}
	return nil
}

// loadGitLabCredentials loads GitLab authentication credentials
func (p *CredentialProvider) loadGitLabCredentials(ctx context.Context) error {
	p.mu.Lock()
	defer p.mu.Unlock()

	// Try to load from secrets manager if available
	if p.secretsManager != nil && p.secretsManager.IsAvailable() {
		creds, err := p.secretsManager.GetCredentials(ctx, "gitlab")
		if err == nil && creds != nil {
			p.credentials[ServiceGitLab] = creds
			p.logger.Info("Loaded GitLab credentials from secrets manager")
			return nil
		}
	}
	
	// Try to load from vault if available
	if p.vaultManager != nil && p.vaultManager.IsAvailable() {
		creds, err := p.vaultManager.GetCredentials(ctx, "gitlab")
		if err == nil && creds != nil {
			p.credentials[ServiceGitLab] = creds
			p.logger.Info("Loaded GitLab credentials from vault")
			return nil
		}
	}

	// Primary source: Environment variables
	token := os.Getenv("GITLAB_AUTH_TOKEN")
	if token != "" {
		p.credentials[ServiceGitLab] = &Credentials{
			Token: token,
		}
		p.logger.Info("Loaded GitLab credentials from environment")
		return nil
	}

	// Secondary source: Config file
	if p.config.GitLab.AuthToken != "" {
		p.credentials[ServiceGitLab] = &Credentials{
			Token: p.config.GitLab.AuthToken,
		}
		p.logger.Info("Loaded GitLab credentials from config file")
		return nil
	}

	p.logger.Warn("No GitLab credentials found, continuing without them")
	// We don't want to fail if GitLab credentials are not found
	// since GitLab integration is optional
	p.credentials[ServiceGitLab] = &Credentials{}
	return nil
}

// loadClaudeCredentials loads Claude API credentials
func (p *CredentialProvider) loadClaudeCredentials(ctx context.Context) error {
	p.mu.Lock()
	defer p.mu.Unlock()

	// Try to load from secrets manager if available
	if p.secretsManager != nil && p.secretsManager.IsAvailable() {
		creds, err := p.secretsManager.GetCredentials(ctx, "claude")
		if err == nil && creds != nil {
			p.credentials[ServiceClaude] = creds
			p.logger.Info("Loaded Claude credentials from secrets manager")
			return nil
		}
	}
	
	// Try to load from vault if available
	if p.vaultManager != nil && p.vaultManager.IsAvailable() {
		creds, err := p.vaultManager.GetCredentials(ctx, "claude")
		if err == nil && creds != nil {
			p.credentials[ServiceClaude] = creds
			p.logger.Info("Loaded Claude credentials from vault")
			return nil
		}
	}

	// Primary source: Environment variables
	apiKey := os.Getenv("CLAUDE_API_KEY")
	if apiKey != "" {
		p.credentials[ServiceClaude] = &Credentials{
			APIKey: apiKey,
		}
		p.logger.Info("Loaded Claude credentials from environment")
		return nil
	}

	// Secondary source: Config file
	if p.config.Claude.APIKey != "" {
		p.credentials[ServiceClaude] = &Credentials{
			APIKey: p.config.Claude.APIKey,
		}
		p.logger.Info("Loaded Claude credentials from config file")
		return nil
	}

	p.logger.Warn("No Claude API key found")
	return fmt.Errorf("no Claude API key found")
}

// RefreshCredentials refreshes credentials for a specific service (for tokens that expire)
func (p *CredentialProvider) RefreshCredentials(ctx context.Context, serviceType ServiceType) error {
	// Implement credential refresh logic based on service type
	switch serviceType {
	case ServiceArgoCD:
		return p.refreshArgoCDToken(ctx)
	default:
		p.logger.Debug("No refresh needed for service", "serviceType", serviceType)
		return nil // No refresh needed for other services
	}
}

// refreshArgoCDToken refreshes the ArgoCD token if using username/password auth
func (p *CredentialProvider) refreshArgoCDToken(ctx context.Context) error {
	p.mu.Lock()
	defer p.mu.Unlock()

	creds, ok := p.credentials[ServiceArgoCD]
	if !ok {
		return fmt.Errorf("ArgoCD credentials not found")
	}

	// If using token authentication and it's not expired, no refresh needed
	if creds.Token != "" && !creds.IsExpired() {
		return nil
	}

	// If using username/password, we would implement logic to get a new token
	if creds.Username != "" && creds.Password != "" {
		p.logger.Info("Refreshing ArgoCD token using username/password")		
		p.logger.Info("Successfully refreshed ArgoCD token")
		return nil
	}

	return fmt.Errorf("unable to refresh ArgoCD token: invalid credential type")
}

// UpdateArgoToken updates the ArgoCD token
func (p *CredentialProvider) UpdateArgoToken(ctx context.Context, token string) {
    p.mu.Lock()
    defer p.mu.Unlock()
    
    if creds, ok := p.credentials[ServiceArgoCD]; ok {
        creds.Token = token
        creds.ExpiresAt = time.Now().Add(24 * time.Hour)
        p.logger.Info("Updated ArgoCD token")
    } else {
        p.credentials[ServiceArgoCD] = &Credentials{
            Token: token,
            ExpiresAt: time.Now().Add(24 * time.Hour),
        }
        p.logger.Info("Created new ArgoCD token")
    }
}
```

--------------------------------------------------------------------------------
/docs/src/content/docs/configuration.md:
--------------------------------------------------------------------------------

```markdown
---
title: Configuration Guide
description: Learn how to configure and customize the Kubernetes Claude MCP server to suit your needs and environment.
date: 2025-03-01
order: 4
tags: ['configuration', 'setup']
---

# Configuration Guide

This guide explains how to configure Kubernetes Claude MCP to work optimally in your environment. The server is highly configurable, allowing you to customize its behavior and integrations.

## Configuration File

Kubernetes Claude MCP is primarily configured using a YAML file (`config.yaml`). This file contains settings for the server, Kubernetes connection, ArgoCD integration, GitLab integration, and Claude AI.

Here's a complete example of the configuration file with explanations:

```yaml
# Server configuration
server:
  # Address to bind the server on (host:port)
  address: ":8080"
  # Read timeout in seconds
  readTimeout: 30
  # Write timeout in seconds
  writeTimeout: 60
  # Authentication settings
  auth:
    # API key for authenticating requests
    apiKey: "your_api_key_here"

# Kubernetes connection settings
kubernetes:
  # Path to kubeconfig file (leave empty for in-cluster)
  kubeconfig: ""
  # Whether to use in-cluster config
  inCluster: false
  # Default Kubernetes context (leave empty for current)
  defaultContext: ""
  # Default namespace
  defaultNamespace: "default"

# ArgoCD integration settings
argocd:
  # ArgoCD server URL
  url: "https://argocd.example.com"
  # ArgoCD auth token (optional if using username/password)
  authToken: ""
  # ArgoCD username (optional if using token)
  username: "admin"
  # ArgoCD password (optional if using token)
  password: "password"
  # Whether to allow insecure connections
  insecure: false

# GitLab integration settings
gitlab:
  # GitLab server URL
  url: "https://gitlab.com"
  # GitLab personal access token
  authToken: "your_gitlab_token"
  # GitLab API version
  apiVersion: "v4"
  # Default project path
  projectPath: "namespace/project"

# Claude AI settings
claude:
  # Claude API key
  apiKey: "your_claude_api_key"
  # Claude API base URL
  baseURL: "https://api.anthropic.com"
  # Claude model ID
  modelID: "claude-3-haiku-20240307"
  # Maximum tokens for Claude responses
  maxTokens: 4096
  # Temperature for Claude responses (0.0-1.0)
  temperature: 0.7
```

## Configuration Options

### Server Configuration

| Option | Description | Default |
|--------|-------------|---------|
| `address` | Host and port to bind the server (":8080" means all interfaces, port 8080) | ":8080" |
| `readTimeout` | HTTP read timeout in seconds | 30 |
| `writeTimeout` | HTTP write timeout in seconds | 60 |
| `auth.apiKey` | API key for authenticating requests | - |

### Kubernetes Configuration

| Option | Description | Default |
|--------|-------------|---------|
| `kubeconfig` | Path to kubeconfig file | "" (auto-detect) |
| `inCluster` | Whether to use in-cluster configuration | false |
| `defaultContext` | Default Kubernetes context | "" (current context) |
| `defaultNamespace` | Default namespace for operations | "default" |

### ArgoCD Configuration

| Option | Description | Default |
|--------|-------------|---------|
| `url` | ArgoCD server URL | - |
| `authToken` | ArgoCD auth token | "" |
| `username` | ArgoCD username | "" |
| `password` | ArgoCD password | "" |
| `insecure` | Allow insecure connections to ArgoCD | false |

### GitLab Configuration

| Option | Description | Default |
|--------|-------------|---------|
| `url` | GitLab server URL | "https://gitlab.com" |
| `authToken` | GitLab personal access token | - |
| `apiVersion` | GitLab API version | "v4" |
| `projectPath` | Default project path | "" |

### Claude Configuration

| Option | Description | Default |
|--------|-------------|---------|
| `apiKey` | Claude API key | - |
| `baseURL` | Claude API base URL | "https://api.anthropic.com" |
| `modelID` | Claude model ID | "claude-3-haiku-20240307" |
| `maxTokens` | Maximum tokens for response | 4096 |
| `temperature` | Temperature for responses (0.0-1.0) | 0.7 |

## Environment Variables

In addition to the configuration file, you can use environment variables to override any configuration option. This is especially useful for secrets and credentials.

Environment variables follow this pattern:

- For server options: `SERVER_OPTION_NAME`
- For Kubernetes options: `KUBERNETES_OPTION_NAME`
- For ArgoCD options: `ARGOCD_OPTION_NAME`
- For GitLab options: `GITLAB_OPTION_NAME`
- For Claude options: `CLAUDE_OPTION_NAME`

Common examples:

```bash
# API keys
export CLAUDE_API_KEY=your_claude_api_key
export API_KEY=your_api_key_for_server

# ArgoCD credentials
export ARGOCD_USERNAME=your_argocd_username
export ARGOCD_PASSWORD=your_argocd_password

# GitLab credentials
export GITLAB_AUTH_TOKEN=your_gitlab_token
```

## Variable Interpolation

The configuration file supports variable interpolation, allowing you to reference environment variables in your config. This is useful for injecting secrets:

```yaml
server:
  auth:
    apiKey: "${API_KEY}"

claude:
  apiKey: "${CLAUDE_API_KEY}"
```

## Configuration Hierarchy

The server reads configuration in the following order (later overrides earlier):

1. Default values
2. Configuration file
3. Environment variables

This allows you to have a base configuration file and override specific settings with environment variables.

## ArgoCD Integration

### Authentication Methods

There are two ways to authenticate with ArgoCD:

1. **Token-based authentication**: Provide an auth token in `argocd.authToken`.
2. **Username/password authentication**: Provide username and password in `argocd.username` and `argocd.password`.

For production environments, token-based authentication is recommended for security.

### Insecure Mode

If you're using a self-signed certificate for ArgoCD, you can set `argocd.insecure` to `true` to skip certificate validation. However, this is not recommended for production environments.

## GitLab Integration

### Personal Access Token

To integrate with GitLab, you need a personal access token with the following scopes:

- `read_api` - For accessing repository information
- `read_repository` - For accessing repository content
- `read_registry` - For accessing container registry (if needed)

### Self-hosted GitLab

If you're using a self-hosted GitLab instance, set the `gitlab.url` to your GitLab URL:

```yaml
gitlab:
  url: "https://gitlab.your-company.com"
  # Other GitLab settings...
```

## Claude AI Configuration

### Model Selection

Kubernetes Claude MCP supports different Claude model variants. The default is `claude-3-haiku-20240307`, but you can choose others based on your needs:

- `claude-3-opus-20240229` - Most capable model, best for complex analysis
- `claude-3-sonnet-20240229` - Balanced performance and speed
- `claude-3-haiku-20240307` - Fastest model, suitable for most use cases

### Response Parameters

You can adjust two parameters that affect Claude's responses:

1. `maxTokens` - Maximum number of tokens in the response (1-4096)
2. `temperature` - Controls randomness in responses (0.0-1.0)
   - Lower values (e.g., 0.3) make responses more deterministic
   - Higher values (e.g., 0.7) make responses more creative

For troubleshooting and analysis, a temperature of 0.3-0.5 is recommended.

## Advanced Configuration

### Running Behind a Proxy

If the server needs to connect to external services through a proxy, set the standard HTTP proxy environment variables:

```bash
export HTTP_PROXY=http://proxy.example.com:8080
export HTTPS_PROXY=http://proxy.example.com:8080
export NO_PROXY=localhost,127.0.0.1,.cluster.local
```

### TLS Configuration

For production deployments, it's recommended to use TLS. This is typically handled by your ingress controller, load balancer, or API gateway.

If you need to terminate TLS at the server (not recommended for production), you can use a reverse proxy like Nginx or Traefik.

### Logging Configuration

The logging level can be controlled with the `LOG_LEVEL` environment variable:

```bash
export LOG_LEVEL=debug  # debug, info, warn, error
```

For production, `info` is recommended. Use `debug` only for troubleshooting.

## Configuration Examples

### Minimal Configuration

```yaml
server:
  address: ":8080"
  auth:
    apiKey: "your_api_key_here"

kubernetes:
  inCluster: false

claude:
  apiKey: "your_claude_api_key"
  modelID: "claude-3-haiku-20240307"
```

### Production Kubernetes Configuration

```yaml
server:
  address: ":8080"
  readTimeout: 60
  writeTimeout: 120
  auth:
    apiKey: "${API_KEY}"

kubernetes:
  inCluster: true
  defaultNamespace: "default"

argocd:
  url: "https://argocd.example.com"
  authToken: "${ARGOCD_AUTH_TOKEN}"
  insecure: false

gitlab:
  url: "https://gitlab.example.com"
  authToken: "${GITLAB_AUTH_TOKEN}"
  apiVersion: "v4"

claude:
  apiKey: "${CLAUDE_API_KEY}"
  baseURL: "https://api.anthropic.com"
  modelID: "claude-3-haiku-20240307"
  maxTokens: 4096
  temperature: 0.5
```

## Troubleshooting Configuration

If you encounter issues with your configuration:

1. Check that all required fields are set correctly
2. Verify that environment variables are correctly set and accessible to the server
3. Test connectivity to external services (Kubernetes, ArgoCD, GitLab)
4. Check the server logs for error messages
5. Ensure your Claude API key is valid and has sufficient quota

### Common Issues

#### "Failed to create Kubernetes client"

This usually indicates an issue with the Kubernetes configuration:

- Check if the kubeconfig file exists and is accessible
- Verify the permissions of the kubeconfig file
- For in-cluster config, ensure the pod has the proper service account

#### "Failed to connect to ArgoCD"

ArgoCD connectivity issues are typically related to:

- Incorrect URL or credentials
- Network connectivity issues
- Certificate validation (if `insecure: false`)

Try using the `--log-level=debug` flag to get more details:

```bash
LOG_LEVEL=debug ./mcp-server --config config.yaml
```

#### "Failed to connect to GitLab"

GitLab connectivity issues may be due to:

- Invalid personal access token
- Insufficient permissions for the token
- Network connectivity issues

#### "Claude API error"

Claude API errors usually indicate:

- Invalid API key
- Rate limiting or quota issues
- Incorrect model ID

## Updating Configuration

You can update the configuration without restarting the server by sending a SIGHUP signal:

```bash
# Find the process ID
ps aux | grep mcp-server

# Send SIGHUP signal
kill -HUP <process_id>
```

For containerized deployments, you'll need to restart the container to apply configuration changes.

## Next Steps

Now that you've configured Kubernetes Claude MCP, you can:

- [Explore the API](/docs/api-overview) to learn how to interact with the server
- [Try some examples](/docs/examples/basic-usage) to see common use cases
- [Learn about troubleshooting](/docs/troubleshooting-resources) to diagnose issues in your cluster
```

--------------------------------------------------------------------------------
/docs/src/content/docs/troubleshooting-resources.md:
--------------------------------------------------------------------------------

```markdown
---
title: Troubleshooting Resources
description: Learn how to use Kubernetes Claude MCP to diagnose and solve problems with your Kubernetes resources and applications.
date: 2025-03-01
order: 6
tags: ['troubleshooting', 'guides']
---

# Troubleshooting Resources

Kubernetes Claude MCP is a powerful tool for diagnosing and resolving issues in your Kubernetes environment. This guide will walk you through common troubleshooting scenarios and how to use the MCP server to address them.

## Getting Started with Troubleshooting

The `/api/v1/mcp/troubleshoot` endpoint is specifically designed for troubleshooting. It automatically:

1. Collects all relevant information about a resource
2. Detects common issues and their severity
3. Correlates information across systems (Kubernetes, ArgoCD, GitLab)
4. Generates recommendations for fixing the issues
5. Provides Claude AI-powered analysis of the problems

## Troubleshooting Common Resource Types

### Troubleshooting Pods

Pods are often the first place to look when troubleshooting application issues.

**Example Request:**

```bash
curl -X POST \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your_api_key" \
  -d '{
    "resource": "pod",
    "name": "my-app-pod",
    "namespace": "default",
    "query": "Why is this pod not starting?"
  }' \
  http://localhost:8080/api/v1/mcp/troubleshoot
```

**What MCP Detects:**

- Pod status issues (Pending, CrashLoopBackOff, ImagePullBackOff, etc.)
- Container status and restart counts
- Resource constraints (CPU/memory limits)
- Volume mounting issues
- Init container failures
- Image pull errors
- Scheduling problems
- Events related to the pod

**Example Troubleshooting Output:**

```json
{
  "success": true,
  "analysis": "The pod 'my-app-pod' is failing to start due to an ImagePullBackOff error. The container runtime is unable to pull the image 'myregistry.com/my-app:v1.2.3' because of authentication issues with the private registry. Looking at the events, there was an 'ErrImagePull' error with the message 'unauthorized: authentication required'...",
  "troubleshootResult": {
    "issues": [
      {
        "title": "Image Pull Error",
        "category": "ImagePullError",
        "severity": "Error",
        "source": "Kubernetes",
        "description": "Failed to pull image 'myregistry.com/my-app:v1.2.3': unauthorized: authentication required"
      }
    ],
    "recommendations": [
      "Create or update the ImagePullSecret for the private registry",
      "Verify the image name and tag are correct",
      "Check that the ServiceAccount has access to the ImagePullSecret"
    ]
  }
}
```

### Troubleshooting Deployments

Deployments manage replica sets and pods, so issues can occur at multiple levels.

**Example Request:**

```bash
curl -X POST \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your_api_key" \
  -d '{
    "resource": "deployment",
    "name": "my-app",
    "namespace": "default",
    "query": "Why are pods not scaling up?"
  }' \
  http://localhost:8080/api/v1/mcp/troubleshoot
```

**What MCP Detects:**

- ReplicaSet creation issues
- Pod scaling issues
- Resource quotas preventing scaling
- Node capacity issues
- Pod disruption budgets
- Deployment strategy issues
- Resource constraints on pods
- Health check configuration issues

**Example Troubleshooting Output:**

```json
{
  "success": true,
  "analysis": "The deployment 'my-app' is unable to scale up because the pods are requesting more CPU resources than are available in the cluster. The deployment is configured to request 2 CPU cores per pod, but the nodes in your cluster only have 1.8 cores available per node...",
  "troubleshootResult": {
    "issues": [
      {
        "title": "Insufficient CPU Resources",
        "category": "ResourceConstraint",
        "severity": "Warning",
        "source": "Kubernetes",
        "description": "Insufficient CPU resources available to schedule pods (requested: 2, available: 1.8)"
      }
    ],
    "recommendations": [
      "Reduce the CPU request in the deployment specification",
      "Add more nodes to the cluster or use nodes with more CPU capacity",
      "Check if there are any resource quotas preventing the scaling"
    ]
  }
}
```

### Troubleshooting Services

Services provide network connectivity between components, and issues often relate to selector mismatches or port configurations.

**Example Request:**

```bash
curl -X POST \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your_api_key" \
  -d '{
    "resource": "service",
    "name": "my-app-service",
    "namespace": "default",
    "query": "Why can't I connect to this service?"
  }' \
  http://localhost:8080/api/v1/mcp/troubleshoot
```

**What MCP Detects:**

- Selector mismatches between service and pods
- Port configuration issues
- Endpoint availability
- Pod readiness issues
- Network policy restrictions
- Service type misconfigurations
- External name resolution issues (for ExternalName services)

**Example Troubleshooting Output:**

```json
{
  "success": true,
  "analysis": "The service 'my-app-service' is not working correctly because there are no endpoints being selected. The service uses the selector 'app=my-app,tier=frontend', but examining the pods in the namespace, I can see that the pods have the labels 'app=my-app,tier=web'. The mismatch in the 'tier' label (frontend vs web) is preventing the service from selecting any pods...",
  "troubleshootResult": {
    "issues": [
      {
        "title": "Selector Mismatch",
        "category": "ServiceSelectorIssue",
        "severity": "Error",
        "source": "Kubernetes",
        "description": "Service selector 'app=my-app,tier=frontend' doesn't match any pods (pods have 'app=my-app,tier=web')"
      }
    ],
    "recommendations": [
      "Update the service selector to match the actual pod labels: 'app=my-app,tier=web'",
      "Alternatively, update the pod labels to match the service selector",
      "Verify that pods are in the 'Running' state and passing readiness probes"
    ]
  }
}
```

### Troubleshooting Ingresses

Ingress resources configure external access to services, and issues often relate to hostname mismatches or TLS configuration.

**Example Request:**

```bash
curl -X POST \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your_api_key" \
  -d '{
    "resource": "ingress",
    "name": "my-app-ingress",
    "namespace": "default",
    "query": "Why is this ingress returning 404 errors?"
  }' \
  http://localhost:8080/api/v1/mcp/troubleshoot
```

**What MCP Detects:**

- Backend service existence and configuration
- Path routing rules
- TLS certificate issues
- Ingress controller availability
- Host name configurations
- Annotation misconfigurations
- Service port mappings

## Troubleshooting GitOps Resources

Kubernetes Claude MCP excels at diagnosing issues in GitOps workflows by correlating information between Kubernetes, ArgoCD, and GitLab.

### Troubleshooting ArgoCD Applications

**Example Request:**

```bash
curl -X POST \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your_api_key" \
  -d '{
    "resource": "application",
    "name": "my-argocd-app",
    "namespace": "argocd",
    "query": "Why is this application out of sync?"
  }' \
  http://localhost:8080/api/v1/mcp/troubleshoot
```

**What MCP Detects:**

- Sync status issues
- Sync history and recent failures
- Git repository connectivity issues
- Manifest validation errors
- Resource differences between desired and actual state
- Health status issues
- Related Kubernetes resources

**Example Troubleshooting Output:**

```json
{
  "success": true,
  "analysis": "The ArgoCD application 'my-argocd-app' is out of sync because there are local changes to the Deployment resource that differ from the version in Git. Specifically, someone has manually scaled the deployment from 3 replicas (as defined in Git) to 5 replicas using kubectl...",
  "troubleshootResult": {
    "issues": [
      {
        "title": "Manual Modification",
        "category": "SyncIssue",
        "severity": "Warning",
        "source": "ArgoCD",
        "description": "Deployment 'my-app' was manually modified: replicas changed from 3 to 5"
      }
    ],
    "recommendations": [
      "Use 'argocd app sync my-argocd-app' to revert to the state defined in Git",
      "Update the Git repository to reflect the desired replica count",
      "Enable self-healing in the ArgoCD application to prevent manual modifications"
    ]
  }
}
```

### Investigating Commit Impact

When a deployment fails after a GitLab commit, you can analyze the commit's impact:

**Example Request:**

```bash
curl -X POST \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your_api_key" \
  -d '{
    "projectId": "mygroup/myproject",
    "commitSha": "abcdef1234567890",
    "query": "How has this commit affected Kubernetes resources and what issues has it caused?"
  }' \
  http://localhost:8080/api/v1/mcp/commit
```

**What MCP Analyzes:**

- Files changed in the commit
- Connected ArgoCD applications
- Affected Kubernetes resources
- Subsequent pipeline results
- Changes in resource configurations
- Introduction of new errors or warnings

## Advanced Troubleshooting Scenarios

### Multi-Resource Analysis

You can troubleshoot complex issues by instructing Claude to correlate multiple resources:

**Example Request:**

```bash
curl -X POST \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your_api_key" \
  -d '{
    "query": "Analyze the connectivity issue between the frontend deployment and the backend service in the 'myapp' namespace. Check both the deployment and the service configurations."
  }' \
  http://localhost:8080/api/v1/mcp
```

### Diagram Generation

For complex troubleshooting scenarios, you can request diagram generation to visualize relationships:

**Example Request:**

```bash
curl -X POST \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your_api_key" \
  -d '{
    "resource": "deployment",
    "name": "my-app",
    "namespace": "default",
    "query": "Create a diagram showing this deployment's relationship to all associated resources, including services, ingresses, configmaps, and secrets."
  }' \
  http://localhost:8080/api/v1/mcp/resource
```

Claude can generate Mermaid diagrams within its response to visualize the relationships.

## Troubleshooting Best Practices

When using Kubernetes Claude MCP for troubleshooting:

1. **Start specific**: Begin with the resource that's showing symptoms
2. **Go broad**: If needed, expand to related resources
3. **Use specific queries**: The more specific your query, the better Claude can help
4. **Include context**: Mention what you've already tried or specific symptoms
5. **Follow recommendations**: Try the recommended fixes one at a time
6. **Iterate**: Use follow-up queries to dive deeper

## Real-Time Troubleshooting

For ongoing issues, you can set up continuous monitoring:

```bash
# Watch a resource and get alerts when issues are detected
watch -n 30 'curl -s -X POST \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your_api_key" \
  -d "{\"resource\":\"deployment\",\"name\":\"my-app\",\"namespace\":\"default\",\"query\":\"Report any new issues\"}" \
  http://localhost:8080/api/v1/mcp/troubleshoot | jq .troubleshootResult.issues'
```

## Troubleshooting Reference

Here's a quick reference of what to check for common Kubernetes issues:

| Symptom | Resource to Check | Common Issues |
|---------|-------------------|---------------|
| Application not starting | Pod | Image pull errors, resource constraints, configuration issues |
| Cannot connect to app | Service | Selector mismatch, port configuration, pod health |
| External access failing | Ingress | Path configuration, backend service, TLS issues |
| Scaling issues | Deployment | Resource constraints, pod disruption budgets, affinity rules |
| Configuration issues | ConfigMap/Secret | Missing keys, invalid format, mounting issues |
| Persistent storage issues | PVC | Storage class, capacity issues, access modes |
| GitOps sync failures | ArgoCD Application | Git repo issues, manifest errors, resource conflicts |
```

--------------------------------------------------------------------------------
/kubernetes-claude-mcp/internal/mcp/context.go:
--------------------------------------------------------------------------------

```go
package mcp

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

    "github.com/Blankcut/kubernetes-mcp-server/kubernetes-claude-mcp/internal/models"
    "github.com/Blankcut/kubernetes-mcp-server/kubernetes-claude-mcp/pkg/logging"
    "github.com/Blankcut/kubernetes-mcp-server/kubernetes-claude-mcp/pkg/utils"
)

// ContextManager handles the creation and management of context for Claude
type ContextManager struct {
	maxContextSize int
	logger         *logging.Logger
}

// NewContextManager creates a new context manager
func NewContextManager(maxContextSize int, logger *logging.Logger) *ContextManager {
	if maxContextSize <= 0 {
		maxContextSize = 100000
	}

	if logger == nil {
		logger = logging.NewLogger().Named("context")
	}

	return &ContextManager{
		maxContextSize: maxContextSize,
		logger:         logger,
	}
}

// FormatResourceContext formats a resource context for Claude
func (cm *ContextManager) FormatResourceContext(rc models.ResourceContext) (string, error) {
    cm.logger.Debug("Formatting resource context", 
        "kind", rc.Kind, 
        "name", rc.Name, 
        "namespace", rc.Namespace)
    
    var formattedContext string

    // Format the basic resource information
    formattedContext += fmt.Sprintf("# Kubernetes Resource: %s/%s\n", rc.Kind, rc.Name)
    if rc.Namespace != "" {
        formattedContext += fmt.Sprintf("Namespace: %s\n", rc.Namespace)
    }
    formattedContext += fmt.Sprintf("API Version: %s\n\n", rc.APIVersion)

	// Add the full resource data if available
	if rc.ResourceData != "" {
		formattedContext += "## Resource Details\n```json\n"
		formattedContext += rc.ResourceData
		formattedContext += "\n```\n\n"
	}

	// Add resource-specific metadata if available
	if rc.Metadata != nil {
		// Add deployment-specific information
		if strings.EqualFold(rc.Kind, "deployment") {
			formattedContext += "## Deployment Status\n"
			
			// Add replica information
			if desiredReplicas, ok := rc.Metadata["desiredReplicas"].(int64); ok {
				formattedContext += fmt.Sprintf("Desired Replicas: %d\n", desiredReplicas)
			}
			
			if currentReplicas, ok := rc.Metadata["currentReplicas"].(int64); ok {
				formattedContext += fmt.Sprintf("Current Replicas: %d\n", currentReplicas)
			}
			
			if readyReplicas, ok := rc.Metadata["readyReplicas"].(int64); ok {
				formattedContext += fmt.Sprintf("Ready Replicas: %d\n", readyReplicas)
			}
			
			if availableReplicas, ok := rc.Metadata["availableReplicas"].(int64); ok {
				formattedContext += fmt.Sprintf("Available Replicas: %d\n", availableReplicas)
			}
			
			// Add container information
			if containers, ok := rc.Metadata["containers"].([]map[string]interface{}); ok && len(containers) > 0 {
				formattedContext += "\n### Containers\n"
				for i, container := range containers {
					formattedContext += fmt.Sprintf("%d. Name: %s\n", i+1, container["name"])
					
					if image, ok := container["image"].(string); ok {
						formattedContext += fmt.Sprintf("   Image: %s\n", image)
					}
					
					if resources, ok := container["resources"].(map[string]interface{}); ok {
						formattedContext += "   Resources:\n"
						
						if requests, ok := resources["requests"].(map[string]interface{}); ok {
							formattedContext += "     Requests:\n"
							for k, v := range requests {
								formattedContext += fmt.Sprintf("       %s: %v\n", k, v)
							}
						}
						
						if limits, ok := resources["limits"].(map[string]interface{}); ok {
							formattedContext += "     Limits:\n"
							for k, v := range limits {
								formattedContext += fmt.Sprintf("       %s: %v\n", k, v)
							}
						}
					}
				}
			}
			
			formattedContext += "\n"
		}
	}

    // If this is a namespace, add namespace-specific information
    if strings.EqualFold(rc.Kind, "namespace") {
        // Add resource metadata if available
        if rc.Metadata != nil {
            if resourceCounts, ok := rc.Metadata["resourceCounts"].(map[string][]string); ok {
                formattedContext += "## Resources in Namespace\n"
                for kind, resources := range resourceCounts {
                    formattedContext += fmt.Sprintf("- %s: %d resources\n", kind, len(resources))
                    
                    // List up to 5 resources of each kind
                    if len(resources) > 0 {
                        formattedContext += "  - "
                        for i, name := range resources {
                            if i > 4 {
                                formattedContext += fmt.Sprintf("and %d more...", len(resources)-5)
                                break
                            }
                            if i > 0 {
                                formattedContext += ", "
                            }
                            formattedContext += name
                        }
                        formattedContext += "\n"
                    }
                }
                formattedContext += "\n"
            }
            
            if health, ok := rc.Metadata["health"].(map[string]map[string]string); ok {
                formattedContext += "## Health Status\n"
                for kind, statuses := range health {
                    healthy := 0
                    unhealthy := 0
                    progressing := 0
                    unknown := 0
                    
                    for _, status := range statuses {
                        switch status {
                        case "healthy":
                            healthy++
                        case "unhealthy":
                            unhealthy++
                        case "progressing":
                            progressing++
                        default:
                            unknown++
                        }
                    }
                    
                    formattedContext += fmt.Sprintf("- %s: %d healthy, %d unhealthy, %d progressing, %d unknown\n", 
                        kind, healthy, unhealthy, progressing, unknown)
                    
                    // List unhealthy resources
                    unhealthyResources := []string{}
                    for name, status := range statuses {
                        if status == "unhealthy" {
                            unhealthyResources = append(unhealthyResources, name)
                        }
                    }
                    
                    if len(unhealthyResources) > 0 {
                        formattedContext += "  Unhealthy: "
                        for i, name := range unhealthyResources {
                            if i > 4 {
                                formattedContext += fmt.Sprintf("and %d more...", len(unhealthyResources)-5)
                                break
                            }
                            if i > 0 {
                                formattedContext += ", "
                            }
                            formattedContext += name
                        }
                        formattedContext += "\n"
                    }
                }
                formattedContext += "\n"
            }
        }
    }

	// Format ArgoCD information if available
	if rc.ArgoApplication != nil {
		formattedContext += "## ArgoCD Application\n"
		formattedContext += fmt.Sprintf("Name: %s\n", rc.ArgoApplication.Name)
		formattedContext += fmt.Sprintf("Sync Status: %s\n", rc.ArgoSyncStatus)
		formattedContext += fmt.Sprintf("Health Status: %s\n", rc.ArgoHealthStatus)
		
		if rc.ArgoApplication.Spec.Source.RepoURL != "" {
			formattedContext += fmt.Sprintf("Source: %s\n", rc.ArgoApplication.Spec.Source.RepoURL)
			formattedContext += fmt.Sprintf("Path: %s\n", rc.ArgoApplication.Spec.Source.Path)
			formattedContext += fmt.Sprintf("Target Revision: %s\n", rc.ArgoApplication.Spec.Source.TargetRevision)
		}
		
		formattedContext += "\n"
		
		// Add recent sync history
		if len(rc.ArgoSyncHistory) > 0 {
			formattedContext += "### Recent Sync History\n"
			for i, history := range rc.ArgoSyncHistory {
				formattedContext += fmt.Sprintf("%d. [%s] Revision: %s, Status: %s\n", 
					i+1, 
					history.DeployedAt.Format(time.RFC3339), 
					history.Revision, 
					history.Status)
			}
			formattedContext += "\n"
		}
	}

	// Format GitLab information if available
	if rc.GitLabProject != nil {
		formattedContext += "## GitLab Project\n"
		formattedContext += fmt.Sprintf("Name: %s\n", rc.GitLabProject.PathWithNamespace)
		formattedContext += fmt.Sprintf("URL: %s\n\n", rc.GitLabProject.WebURL)
		
		// Add last pipeline information
		if rc.LastPipeline != nil {
			formattedContext += "### Last Pipeline\n"
			
			// Handle pipeline CreatedAt timestamp
			var pipelineTimestamp string
			switch createdAt := rc.LastPipeline.CreatedAt.(type) {
			case int64:
				pipelineTimestamp = time.Unix(createdAt, 0).Format(time.RFC3339)
			case float64:
				pipelineTimestamp = time.Unix(int64(createdAt), 0).Format(time.RFC3339)
			case string:
				// Try to parse the string timestamp
				parsed, err := time.Parse(time.RFC3339, createdAt)
				if err != nil {
					// Try alternative format
					parsed, err = time.Parse("2006-01-02T15:04:05.000Z", createdAt)
					if err != nil {
						// Use raw string if parsing fails
						pipelineTimestamp = createdAt
					} else {
						pipelineTimestamp = parsed.Format(time.RFC3339)
					}
				} else {
					pipelineTimestamp = parsed.Format(time.RFC3339)
				}
			default:
				pipelineTimestamp = "unknown timestamp"
			}
			
			formattedContext += fmt.Sprintf("Status: %s\n", rc.LastPipeline.Status)
			formattedContext += fmt.Sprintf("Ref: %s\n", rc.LastPipeline.Ref)
			formattedContext += fmt.Sprintf("SHA: %s\n", rc.LastPipeline.SHA)
			formattedContext += fmt.Sprintf("Created At: %s\n\n", pipelineTimestamp)
		}
		
		// Add last deployment information
		if rc.LastDeployment != nil {
			formattedContext += "### Last Deployment\n"
			
			// Handle deployment CreatedAt timestamp
			var deploymentTimestamp string
			switch createdAt := rc.LastDeployment.CreatedAt.(type) {
			case int64:
				deploymentTimestamp = time.Unix(createdAt, 0).Format(time.RFC3339)
			case float64:
				deploymentTimestamp = time.Unix(int64(createdAt), 0).Format(time.RFC3339)
			case string:
				// Try to parse the string timestamp
				parsed, err := time.Parse(time.RFC3339, createdAt)
				if err != nil {
					// Try alternative format
					parsed, err = time.Parse("2006-01-02T15:04:05.000Z", createdAt)
					if err != nil {
						// Use raw string if parsing fails
						deploymentTimestamp = createdAt
					} else {
						deploymentTimestamp = parsed.Format(time.RFC3339)
					}
				} else {
					deploymentTimestamp = parsed.Format(time.RFC3339)
				}
			default:
				deploymentTimestamp = "unknown timestamp"
			}
			
			formattedContext += fmt.Sprintf("Status: %s\n", rc.LastDeployment.Status)
			formattedContext += fmt.Sprintf("Environment: %s\n", rc.LastDeployment.Environment.Name)
			formattedContext += fmt.Sprintf("Created At: %s\n\n", deploymentTimestamp)
		}
		
		// Add recent commits
		if len(rc.RecentCommits) > 0 {
			formattedContext += "### Recent Commits\n"
			for i, commit := range rc.RecentCommits {
				// Handle commit CreatedAt timestamp
				var commitTimestamp string
				switch createdAt := commit.CreatedAt.(type) {
				case int64:
					commitTimestamp = time.Unix(createdAt, 0).Format(time.RFC3339)
				case float64:
					commitTimestamp = time.Unix(int64(createdAt), 0).Format(time.RFC3339)
				case string:
					// Try to parse the string timestamp
					parsed, err := time.Parse(time.RFC3339, createdAt)
					if err != nil {
						// Try alternative format
						parsed, err = time.Parse("2006-01-02T15:04:05.000Z", createdAt)
						if err != nil {
							// Use raw string if parsing fails
							commitTimestamp = createdAt
						} else {
							commitTimestamp = parsed.Format(time.RFC3339)
						}
					} else {
						commitTimestamp = parsed.Format(time.RFC3339)
					}
				default:
					commitTimestamp = "unknown timestamp"
				}
				
				formattedContext += fmt.Sprintf("%d. [%s] %s by %s: %s\n", 
					i+1, 
					commitTimestamp, 
					commit.ShortID, 
					commit.AuthorName, 
					commit.Title)
			}
			formattedContext += "\n"
		}
	}

	// Format Kubernetes events
	if len(rc.Events) > 0 {
		formattedContext += "## Recent Kubernetes Events\n"
		for i, event := range rc.Events {
			formattedContext += fmt.Sprintf("%d. [%s] %s: %s\n", 
				i+1, 
				event.Type, 
				event.Reason, 
				event.Message)
		}
		formattedContext += "\n"
	}

	if len(rc.RelatedResources) > 0 {
        formattedContext += "## Related Resources\n"
        // Group by resource kind
        resourcesByKind := make(map[string][]string)
        for _, resource := range rc.RelatedResources {
            parts := strings.Split(resource, "/")
            if len(parts) == 2 {
                kind := parts[0]
                name := parts[1]
                resourcesByKind[kind] = append(resourcesByKind[kind], name)
            } else {
                // If format is unexpected, just add as is
                formattedContext += fmt.Sprintf("- %s\n", resource)
            }
        }
        
        // Format resources by kind
        for kind, names := range resourcesByKind {
            formattedContext += fmt.Sprintf("- %s (%d):\n", kind, len(names))
            // Show up to 10 resources per kind
            maxToShow := 10
            if len(names) > maxToShow {
                for i := 0; i < maxToShow; i++ {
                    formattedContext += fmt.Sprintf("  - %s\n", names[i])
                }
                formattedContext += fmt.Sprintf("  - ... and %d more\n", len(names)-maxToShow)
            } else {
                for _, name := range names {
                    formattedContext += fmt.Sprintf("  - %s\n", name)
                }
            }
        }
        formattedContext += "\n"
    }

	// Add errors if any
	if len(rc.Errors) > 0 {
		formattedContext += "## Errors in Data Collection\n"
		for _, err := range rc.Errors {
			formattedContext += fmt.Sprintf("- %s\n", err)
		}
		formattedContext += "\n"
	}

	// Ensure context doesn't exceed max size
	if len(formattedContext) > cm.maxContextSize {
        cm.logger.Debug("Context exceeds maximum size, truncating", 
            "originalSize", len(formattedContext), 
            "maxSize", cm.maxContextSize)
        formattedContext = utils.TruncateContextSmartly(formattedContext, cm.maxContextSize)
    }

	cm.logger.Debug("Formatted resource context", 
        "kind", rc.Kind, 
        "name", rc.Name, 
        "contextSize", len(formattedContext))
    return formattedContext, nil
}

// CombineContexts combines multiple resource contexts into a single context
func (cm *ContextManager) CombineContexts(ctx context.Context, resourceContexts []models.ResourceContext) (string, error) {
	cm.logger.Debug("Combining resource contexts", "count", len(resourceContexts))
	
	var combinedContext string
	
	combinedContext += fmt.Sprintf("# Kubernetes GitOps Context (%d resources)\n\n", len(resourceContexts))
	
	// Add context for each resource
	for i, rc := range resourceContexts {
		resourceContext, err := cm.FormatResourceContext(rc)
		if err != nil {
			return "", fmt.Errorf("failed to format resource context #%d: %w", i+1, err)
		}
		
		combinedContext += fmt.Sprintf("--- RESOURCE %d/%d ---\n", i+1, len(resourceContexts))
		combinedContext += resourceContext
		combinedContext += "------------------------\n\n"
	}
	
	// Ensure combined context doesn't exceed max size
	if len(combinedContext) > cm.maxContextSize {
		cm.logger.Debug("Combined context exceeds maximum size, truncating", 
			"originalSize", len(combinedContext), 
			"maxSize", cm.maxContextSize)
		combinedContext = utils.TruncateContextSmartly(combinedContext, cm.maxContextSize)
	}
	
	cm.logger.Debug("Combined resource contexts", 
		"resourceCount", len(resourceContexts), 
		"contextSize", len(combinedContext))
	return combinedContext, nil
}
```

--------------------------------------------------------------------------------
/kubernetes-claude-mcp/internal/api/routes.go:
--------------------------------------------------------------------------------

```go
package api

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strings"

	"github.com/Blankcut/kubernetes-mcp-server/kubernetes-claude-mcp/internal/models"
	"github.com/gorilla/mux"
)

// setupRoutes configures the API routes
func (s *Server) setupRoutes() {

	// Apply CORS middleware to all routes
	s.router.Use(s.corsMiddleware)

	// API version prefix
	apiV1 := s.router.PathPrefix("/api/v1").Subrouter()

	// Health check endpoint (no auth required)
	apiV1.HandleFunc("/health", s.handleHealth).Methods("GET")

	// Add authentication middleware to all other routes
	apiSecure := apiV1.NewRoute().Subrouter()
	apiSecure.Use(s.authMiddleware)

	// MCP endpoints
	apiSecure.HandleFunc("/mcp", s.handleMCPRequest).Methods("POST")
	apiSecure.HandleFunc("/mcp/resource", s.handleResourceQuery).Methods("POST")
	apiSecure.HandleFunc("/mcp/commit", s.handleCommitQuery).Methods("POST")
	apiSecure.HandleFunc("/mcp/troubleshoot", s.handleTroubleshoot).Methods("POST")

	// Kubernetes resource endpoints
	apiSecure.HandleFunc("/namespaces", s.handleListNamespaces).Methods("GET")
	apiSecure.HandleFunc("/resources/{resource}", s.handleListResources).Methods("GET")
	apiSecure.HandleFunc("/resources/{resource}/{name}", s.handleGetResource).Methods("GET")
	apiSecure.HandleFunc("/events", s.handleGetEvents).Methods("GET")

	// ArgoCD endpoints
	apiSecure.HandleFunc("/argocd/applications", s.handleListArgoApplications).Methods("GET")
	apiSecure.HandleFunc("/argocd/applications/{name}", s.handleGetArgoApplication).Methods("GET")

	// GitLab endpoints
	apiSecure.HandleFunc("/gitlab/projects", s.handleListGitLabProjects).Methods("GET")
	apiSecure.HandleFunc("/gitlab/projects/{projectId}/pipelines", s.handleListGitLabPipelines).Methods("GET")

	// Merge Request endpoints
	apiSecure.HandleFunc("/mcp/mergeRequest", s.handleMergeRequestQuery).Methods("POST")
}

// handleMergeRequestQuery handles MCP requests for analyzing merge requests
func (s *Server) handleMergeRequestQuery(w http.ResponseWriter, r *http.Request) {
	var request models.MCPRequest

	if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
		s.respondWithError(w, http.StatusBadRequest, "Invalid request format", err)
		return
	}

	// Force action to be queryMergeRequest
	request.Action = "queryMergeRequest"

	// Validate merge request parameters
	if request.ProjectID == "" || request.MergeRequestIID <= 0 {
		s.respondWithError(w, http.StatusBadRequest, "Project ID and merge request IID are required", nil)
		return
	}

	s.logger.Info("Received merge request query",
		"projectId", request.ProjectID,
		"mergeRequestIID", request.MergeRequestIID)

	// Process the request
	response, err := s.mcpHandler.ProcessRequest(r.Context(), &request)
	if err != nil {
		s.respondWithError(w, http.StatusInternalServerError, "Failed to process request", err)
		return
	}

	s.respondWithJSON(w, http.StatusOK, response)
}

// handleHealth handles health check requests
func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) {
	type healthResponse struct {
		Status   string            `json:"status"`
		Services map[string]string `json:"services"`
	}

	// Check each service
	services := map[string]string{
		"kubernetes": "unknown",
		"argocd":     "unknown",
		"gitlab":     "unknown",
		"claude":     "unknown",
	}

	ctx := r.Context()

	// Check Kubernetes connectivity
	if err := s.k8sClient.CheckConnectivity(ctx); err != nil {
		services["kubernetes"] = "unavailable"
		s.logger.Warn("Kubernetes health check failed", "error", err)
	} else {
		services["kubernetes"] = "available"
	}

	// Check ArgoCD connectivity
	if err := s.argoClient.CheckConnectivity(ctx); err != nil {
		services["argocd"] = "unavailable"
		s.logger.Warn("ArgoCD health check failed", "error", err)
	} else {
		services["argocd"] = "available"
	}

	// Check GitLab connectivity
	if err := s.gitlabClient.CheckConnectivity(ctx); err != nil {
		services["gitlab"] = "unavailable"
		s.logger.Warn("GitLab health check failed", "error", err)
	} else {
		services["gitlab"] = "available"
	}

	// For Claude, we just assume it's available since we don't want to make an API call
	// in a health check endpoint
	services["claude"] = "assumed available"

	// Determine overall status
	status := "ok"
	if services["kubernetes"] != "available" {
		status = "degraded"
	}

	response := healthResponse{
		Status:   status,
		Services: services,
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(response)
}

// handleMCPRequest handles generic MCP requests
func (s *Server) handleMCPRequest(w http.ResponseWriter, r *http.Request) {
	var request models.MCPRequest

	if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
		s.respondWithError(w, http.StatusBadRequest, "Invalid request format", err)
		return
	}

	s.logger.Info("Received MCP request", "action", request.Action)

	// Process the request
	response, err := s.mcpHandler.ProcessRequest(r.Context(), &request)
	if err != nil {
		s.respondWithError(w, http.StatusInternalServerError, "Failed to process request", err)
		return
	}

	s.respondWithJSON(w, http.StatusOK, response)
}

// handleResourceQuery handles MCP requests for querying resources
func (s *Server) handleResourceQuery(w http.ResponseWriter, r *http.Request) {
	var request models.MCPRequest

	if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
		s.respondWithError(w, http.StatusBadRequest, "Invalid request format", err)
		return
	}

	// Force action to be queryResource
	request.Action = "queryResource"

	// Validate resource parameters
	if request.Resource == "" || request.Name == "" {
		s.respondWithError(w, http.StatusBadRequest, "Resource and name are required", nil)
		return
	}

	s.logger.Info("Received resource query",
		"resource", request.Resource,
		"name", request.Name,
		"namespace", request.Namespace)

	// Special handling for namespace resources to provide comprehensive data
	if strings.ToLower(request.Resource) == "namespace" {
		// Get namespace topology
		topology, err := s.k8sClient.GetNamespaceTopology(r.Context(), request.Name)
		if err != nil {
			s.respondWithError(w, http.StatusInternalServerError, "Failed to get namespace topology", err)
			return
		}

		// Get all resources in the namespace
		resources, err := s.k8sClient.GetAllNamespaceResources(r.Context(), request.Name)
		if err != nil {
			s.respondWithError(w, http.StatusInternalServerError, "Failed to get namespace resources", err)
			return
		}

		// Get namespace analysis
		analysis, err := s.mcpHandler.AnalyzeNamespace(r.Context(), request.Name)
		if err != nil {
			s.respondWithError(w, http.StatusInternalServerError, "Failed to analyze namespace", err)
			return
		}

		// Create an enhanced request with the gathered data
		enhancedRequest := request
		enhancedRequest.Context = fmt.Sprintf("# Namespace Analysis: %s\n\n", request.Name)
		enhancedRequest.Context += fmt.Sprintf("## Resource Counts\n")
		for kind, count := range resources.Stats {
			enhancedRequest.Context += fmt.Sprintf("- %s: %d\n", kind, count)
		}
		enhancedRequest.Context += "\n## Resource Relationships\n"
		for _, rel := range topology.Relationships {
			enhancedRequest.Context += fmt.Sprintf("- %s/%s → %s/%s (%s)\n",
				rel.SourceKind, rel.SourceName, rel.TargetKind, rel.TargetName, rel.RelationType)
		}
		enhancedRequest.Context += "\n## Health Status\n"
		for kind, statuses := range topology.Health {
			healthy := 0
			unhealthy := 0
			progressing := 0
			unknown := 0

			for _, status := range statuses {
				switch status {
				case "healthy":
					healthy++
				case "unhealthy":
					unhealthy++
				case "progressing":
					progressing++
				default:
					unknown++
				}
			}

			enhancedRequest.Context += fmt.Sprintf("- %s: %d healthy, %d unhealthy, %d progressing, %d unknown\n",
				kind, healthy, unhealthy, progressing, unknown)
		}

		// Get events for the namespace
		events, err := s.k8sClient.GetNamespaceEvents(r.Context(), request.Name)
		if err == nil && len(events) > 0 {
			enhancedRequest.Context += "\n## Recent Events\n"
			for i, event := range events {
				if i >= 10 {
					break // Limit to 10 events
				}
				enhancedRequest.Context += fmt.Sprintf("- [%s] %s: %s\n",
					event.Type, event.Reason, event.Message)
			}
		}

		// Process the enhanced request
		response, err := s.mcpHandler.ProcessRequest(r.Context(), &enhancedRequest)
		if err != nil {
			s.respondWithError(w, http.StatusInternalServerError, "Failed to process request", err)
			return
		}

		// Add analysis insights to the response
		if analysis != nil {
			response.NamespaceAnalysis = analysis
		}

		s.respondWithJSON(w, http.StatusOK, response)
		return
	}

	// Process regular resource query
	response, err := s.mcpHandler.ProcessRequest(r.Context(), &request)
	if err != nil {
		s.respondWithError(w, http.StatusInternalServerError, "Failed to process request", err)
		return
	}

	s.respondWithJSON(w, http.StatusOK, response)
}

// handleCommitQuery handles MCP requests for analyzing commits
func (s *Server) handleCommitQuery(w http.ResponseWriter, r *http.Request) {
	var request models.MCPRequest

	if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
		s.respondWithError(w, http.StatusBadRequest, "Invalid request format", err)
		return
	}

	// Force action to be queryCommit
	request.Action = "queryCommit"

	// Validate commit parameters
	if request.ProjectID == "" || request.CommitSHA == "" {
		s.respondWithError(w, http.StatusBadRequest, "Project ID and commit SHA are required", nil)
		return
	}

	s.logger.Info("Received commit query",
		"projectId", request.ProjectID,
		"commitSha", request.CommitSHA)

	// Process the request
	response, err := s.mcpHandler.ProcessRequest(r.Context(), &request)
	if err != nil {
		s.respondWithError(w, http.StatusInternalServerError, "Failed to process request", err)
		return
	}

	s.respondWithJSON(w, http.StatusOK, response)
}

// handleTroubleshoot handles troubleshooting requests
func (s *Server) handleTroubleshoot(w http.ResponseWriter, r *http.Request) {
	var request struct {
		Resource  string `json:"resource"`
		Name      string `json:"name"`
		Namespace string `json:"namespace"`
		Query     string `json:"query,omitempty"`
	}

	if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
		s.respondWithError(w, http.StatusBadRequest, "Invalid request format", err)
		return
	}

	// Validate parameters
	if request.Resource == "" || request.Name == "" {
		s.respondWithError(w, http.StatusBadRequest, "Resource and name are required", nil)
		return
	}

	s.logger.Info("Received troubleshoot request",
		"resource", request.Resource,
		"name", request.Name,
		"namespace", request.Namespace)

	// Process the troubleshooting request
	result, err := s.troubleshootCorrelator.TroubleshootResource(
		r.Context(),
		request.Namespace,
		request.Resource,
		request.Name,
	)
	if err != nil {
		s.respondWithError(w, http.StatusInternalServerError, "Failed to troubleshoot resource", err)
		return
	}

	// If there's a query, use Claude to analyze the results
	if request.Query != "" {
		mcpRequest := &models.MCPRequest{
			Resource:  request.Resource,
			Name:      request.Name,
			Namespace: request.Namespace,
			Query:     request.Query,
		}

		response, err := s.mcpHandler.ProcessTroubleshootRequest(r.Context(), mcpRequest, result)
		if err != nil {
			s.respondWithError(w, http.StatusInternalServerError, "Failed to process troubleshoot analysis", err)
			return
		}

		// Add the troubleshoot result to the response
		responseWithResult := struct {
			*models.MCPResponse
			TroubleshootResult *models.TroubleshootResult `json:"troubleshootResult"`
		}{
			MCPResponse:        response,
			TroubleshootResult: result,
		}

		s.respondWithJSON(w, http.StatusOK, responseWithResult)
		return
	}

	// If no query, just return the troubleshoot result
	s.respondWithJSON(w, http.StatusOK, result)
}

// handleListNamespaces handles requests to list namespaces
func (s *Server) handleListNamespaces(w http.ResponseWriter, r *http.Request) {
	namespaces, err := s.k8sClient.GetNamespaces(r.Context())
	if err != nil {
		s.respondWithError(w, http.StatusInternalServerError, "Failed to list namespaces", err)
		return
	}

	s.respondWithJSON(w, http.StatusOK, map[string][]string{"namespaces": namespaces})
}

// handleListResources handles requests to list resources of a specific type
func (s *Server) handleListResources(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	resourceType := vars["resource"]
	namespace := r.URL.Query().Get("namespace")

	resources, err := s.k8sClient.ListResources(r.Context(), resourceType, namespace)
	if err != nil {
		s.respondWithError(w, http.StatusInternalServerError, "Failed to list resources", err)
		return
	}

	s.respondWithJSON(w, http.StatusOK, map[string]interface{}{"resources": resources})
}

// handleGetResource handles requests to get a specific resource
func (s *Server) handleGetResource(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	resourceType := vars["resource"]
	name := vars["name"]
	namespace := r.URL.Query().Get("namespace")

	resource, err := s.k8sClient.GetResource(r.Context(), resourceType, namespace, name)
	if err != nil {
		s.respondWithError(w, http.StatusInternalServerError, "Failed to get resource", err)
		return
	}

	s.respondWithJSON(w, http.StatusOK, resource)
}

// handleGetEvents handles requests to get events
func (s *Server) handleGetEvents(w http.ResponseWriter, r *http.Request) {
	namespace := r.URL.Query().Get("namespace")
	resourceType := r.URL.Query().Get("resource")
	name := r.URL.Query().Get("name")

	events, err := s.k8sClient.GetResourceEvents(r.Context(), namespace, resourceType, name)
	if err != nil {
		s.respondWithError(w, http.StatusInternalServerError, "Failed to get events", err)
		return
	}

	s.respondWithJSON(w, http.StatusOK, map[string]interface{}{"events": events})
}

// handleListArgoApplications handles requests to list ArgoCD applications
func (s *Server) handleListArgoApplications(w http.ResponseWriter, r *http.Request) {
	applications, err := s.argoClient.ListApplications(r.Context())
	if err != nil {
		s.respondWithError(w, http.StatusInternalServerError, "Failed to list ArgoCD applications", err)
		return
	}

	s.respondWithJSON(w, http.StatusOK, map[string]interface{}{"applications": applications})
}

// handleGetArgoApplication handles requests to get a specific ArgoCD application
func (s *Server) handleGetArgoApplication(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	name := vars["name"]

	application, err := s.argoClient.GetApplication(r.Context(), name)
	if err != nil {
		s.respondWithError(w, http.StatusInternalServerError, "Failed to get ArgoCD application", err)
		return
	}

	s.respondWithJSON(w, http.StatusOK, application)
}

// handleListGitLabProjects handles requests to list GitLab projects
func (s *Server) handleListGitLabProjects(w http.ResponseWriter, r *http.Request) {
	// This would typically include pagination parameters
	projects, err := s.gitlabClient.ListProjects(r.Context())
	if err != nil {
		s.respondWithError(w, http.StatusInternalServerError, "Failed to list GitLab projects", err)
		return
	}

	s.respondWithJSON(w, http.StatusOK, map[string]interface{}{"projects": projects})
}

// handleListGitLabPipelines handles requests to list GitLab pipelines
func (s *Server) handleListGitLabPipelines(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	projectId := vars["projectId"]

	pipelines, err := s.gitlabClient.ListPipelines(r.Context(), projectId)
	if err != nil {
		s.respondWithError(w, http.StatusInternalServerError, "Failed to list GitLab pipelines", err)
		return
	}

	s.respondWithJSON(w, http.StatusOK, map[string]interface{}{"pipelines": pipelines})
}

// Helper methods

// respondWithError sends an error response to the client
func (s *Server) respondWithError(w http.ResponseWriter, code int, message string, err error) {
	errorResponse := map[string]string{
		"error": message,
	}

	if err != nil {
		errorResponse["details"] = err.Error()
		s.logger.Error(message, "error", err, "code", code)
	} else {
		s.logger.Warn(message, "code", code)
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(code)
	json.NewEncoder(w).Encode(errorResponse)
}

// respondWithJSON sends a JSON response to the client
func (s *Server) respondWithJSON(w http.ResponseWriter, code int, payload interface{}) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(code)
	json.NewEncoder(w).Encode(payload)
}

```

--------------------------------------------------------------------------------
/kubernetes-claude-mcp/internal/correlator/gitops.go:
--------------------------------------------------------------------------------

```go
package correlator

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

	"github.com/Blankcut/kubernetes-mcp-server/kubernetes-claude-mcp/internal/argocd"
	"github.com/Blankcut/kubernetes-mcp-server/kubernetes-claude-mcp/internal/gitlab"
	"github.com/Blankcut/kubernetes-mcp-server/kubernetes-claude-mcp/internal/k8s"
	"github.com/Blankcut/kubernetes-mcp-server/kubernetes-claude-mcp/internal/models"
	"github.com/Blankcut/kubernetes-mcp-server/kubernetes-claude-mcp/pkg/logging"
)

// GitOpsCorrelator correlates data between Kubernetes, ArgoCD, and GitLab
type GitOpsCorrelator struct {
	k8sClient      *k8s.Client
	argoClient     *argocd.Client
	gitlabClient   *gitlab.Client
	helmCorrelator *HelmCorrelator
	logger         *logging.Logger
}

// NewGitOpsCorrelator creates a new GitOps correlator
func NewGitOpsCorrelator(k8sClient *k8s.Client, argoClient *argocd.Client, gitlabClient *gitlab.Client, logger *logging.Logger) *GitOpsCorrelator {
	if logger == nil {
		logger = logging.NewLogger().Named("correlator")
	}

	correlator := &GitOpsCorrelator{
		k8sClient:    k8sClient,
		argoClient:   argoClient,
		gitlabClient: gitlabClient,
		logger:       logger,
	}

	// Initialize the Helm correlator
	correlator.helmCorrelator = NewHelmCorrelator(gitlabClient, logger.Named("helm"))

	return correlator
}

// Add a new method to analyze merge requests
func (c *GitOpsCorrelator) AnalyzeMergeRequest(
	ctx context.Context,
	projectID string,
	mergeRequestIID int,
) ([]models.ResourceContext, error) {
	c.logger.Info("Analyzing merge request", "projectID", projectID, "mergeRequestIID", mergeRequestIID)

	// Get merge request details
	mergeRequest, err := c.gitlabClient.AnalyzeMergeRequest(ctx, projectID, mergeRequestIID)
	if err != nil {
		return nil, fmt.Errorf("failed to analyze merge request: %w", err)
	}

	// Check if the MR affects Helm charts or Kubernetes manifests
	if !mergeRequest.MergeRequestContext.HelmChartAffected && !mergeRequest.MergeRequestContext.KubernetesManifest {
		c.logger.Info("Merge request does not affect Kubernetes resources")
		return []models.ResourceContext{}, nil
	}

	// Get all ArgoCD applications
	argoApps, err := c.argoClient.ListApplications(ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to list ArgoCD applications: %w", err)
	}

	// Find the project path
	projectPath := fmt.Sprintf("%s", projectID)
	project, err := c.gitlabClient.GetProject(ctx, projectID)
	if err == nil && project != nil {
		projectPath = project.PathWithNamespace
	}

	// For Helm-affected MRs, analyze Helm changes
	var helmAffectedResources []string
	if mergeRequest.MergeRequestContext.HelmChartAffected {
		helmResources, err := c.helmCorrelator.AnalyzeMergeRequestHelmChanges(ctx, projectID, mergeRequestIID)
		if err != nil {
			c.logger.Warn("Failed to analyze Helm changes in MR", "error", err)
		} else if len(helmResources) > 0 {
			helmAffectedResources = helmResources
			c.logger.Info("Found resources affected by Helm changes in MR", "count", len(helmResources))
		}
	}

	// Identify potentially affected applications
	var affectedApps []models.ArgoApplication
	for _, app := range argoApps {
		if isAppSourcedFromProject(app, projectPath) {
			// For each file changed in the MR, check if it affects the app
			isAffected := false

			// Check if any changed file affects the app
			for _, file := range mergeRequest.MergeRequestContext.AffectedFiles {
				if isFileInAppSourcePath(app, file) {
					isAffected = true
					break
				}
			}

			// Check Helm-derived resources
			if !isAffected && len(helmAffectedResources) > 0 {
				if appContainsAnyResource(ctx, c.argoClient, app, helmAffectedResources) {
					isAffected = true
				}
			}

			if isAffected {
				affectedApps = append(affectedApps, app)
			}
		}
	}

	// For each affected app, identify the resources that would be affected
	var result []models.ResourceContext
	for _, app := range affectedApps {
		c.logger.Info("Found potentially affected ArgoCD application", "app", app.Name)

		// Get resources managed by this application
		tree, err := c.argoClient.GetResourceTree(ctx, app.Name)
		if err != nil {
			c.logger.Warn("Failed to get resource tree", "app", app.Name, "error", err)
			continue
		}

		// For each resource in the app, create a deployment info object
		for _, node := range tree.Nodes {
			// Skip non-Kubernetes resources or resources with no name/namespace
			if node.Kind == "" || node.Name == "" {
				continue
			}

			// Avoid unnecessary duplicates in the result
			if isResourceAlreadyInResults(result, node.Kind, node.Name, node.Namespace) {
				continue
			}

			// Trace the deployment for this resource
			resourceContext, err := c.TraceResourceDeployment(
				ctx,
				node.Namespace,
				node.Kind,
				node.Name,
			)
			if err != nil {
				c.logger.Warn("Failed to trace resource deployment",
					"kind", node.Kind,
					"name", node.Name,
					"namespace", node.Namespace,
					"error", err)
				continue
			}

			// Add source info
			resourceContext.RelatedResources = append(resourceContext.RelatedResources,
				fmt.Sprintf("MergeRequest/%d", mergeRequestIID))

			// Add to results
			result = append(result, resourceContext)
		}
	}

	// Add cleanup on exit
	defer func() {
		if c.helmCorrelator != nil {
			c.helmCorrelator.Cleanup()
		}
	}()

	c.logger.Info("Analysis of merge request completed",
		"projectID", projectID,
		"mergeRequestIID", mergeRequestIID,
		"resourceCount", len(result))

	return result, nil
}

func (c *GitOpsCorrelator) TraceResourceDeployment(
	ctx context.Context,
	namespace, kind, name string,
) (models.ResourceContext, error) {
	c.logger.Info("Tracing resource deployment", "kind", kind, "name", name, "namespace", namespace)

	resourceContext := models.ResourceContext{
		Kind:      kind,
		Name:      name,
		Namespace: namespace,
	}

	var errors []string

	// Get Kubernetes resource information
	resource, err := c.k8sClient.GetResource(ctx, kind, namespace, name)
	if err != nil {
		errMsg := fmt.Sprintf("Failed to get Kubernetes resource: %v", err)
		errors = append(errors, errMsg)
		c.logger.Warn(errMsg)
	} else {
		resourceContext.APIVersion = resource.GetAPIVersion()

		// Get events related to this resource
		events, err := c.k8sClient.GetResourceEvents(ctx, namespace, kind, name)
		if err != nil {
			errMsg := fmt.Sprintf("Failed to get resource events: %v", err)
			errors = append(errors, errMsg)
			c.logger.Warn(errMsg)
		} else {
			resourceContext.Events = events
		}
	}

	// Find the ArgoCD application managing this resource
	argoApps, err := c.argoClient.FindApplicationsByResource(ctx, kind, name, namespace)
	if err != nil {
		errMsg := fmt.Sprintf("Failed to find ArgoCD applications: %v", err)
		errors = append(errors, errMsg)
		c.logger.Warn(errMsg)
	} else if len(argoApps) > 0 {
		// Use the first application that manages this resource
		app := argoApps[0]
		resourceContext.ArgoApplication = &app
		resourceContext.ArgoSyncStatus = app.Status.Sync.Status
		resourceContext.ArgoHealthStatus = app.Status.Health.Status

		// Get recent syncs
		history, err := c.argoClient.GetApplicationHistory(ctx, app.Name)
		if err != nil {
			errMsg := fmt.Sprintf("Failed to get application history: %v", err)
			errors = append(errors, errMsg)
			c.logger.Warn(errMsg)
		} else {
			// Limit to recent syncs (last 5)
			if len(history) > 5 {
				history = history[:5]
			}
			resourceContext.ArgoSyncHistory = history
		}

		// Connect to GitLab if we have source information
		if app.Spec.Source.RepoURL != "" {
			// Extract GitLab project path from repo URL
			projectPath := extractGitLabProjectPath(app.Spec.Source.RepoURL)
			if projectPath != "" {
				project, err := c.gitlabClient.GetProjectByPath(ctx, projectPath)
				if err != nil {
					errMsg := fmt.Sprintf("Failed to get GitLab project: %v", err)
					errors = append(errors, errMsg)
					c.logger.Warn(errMsg)
				} else {
					resourceContext.GitLabProject = project

					// Get recent pipelines
					pipelines, err := c.gitlabClient.ListPipelines(ctx, fmt.Sprintf("%d", project.ID))
					if err != nil {
						errMsg := fmt.Sprintf("Failed to list pipelines: %v", err)
						errors = append(errors, errMsg)
						c.logger.Warn(errMsg)
					} else {
						// Get the latest pipeline
						if len(pipelines) > 0 {
							resourceContext.LastPipeline = &pipelines[0]
						}
					}

					// Find environment from ArgoCD application
					environment := extractEnvironmentFromArgoApp(app)
					if environment != "" {
						// Get recent deployments to this environment
						deployments, err := c.gitlabClient.FindRecentDeployments(
							ctx,
							fmt.Sprintf("%d", project.ID),
							environment,
						)
						if err != nil {
							errMsg := fmt.Sprintf("Failed to find deployments: %v", err)
							errors = append(errors, errMsg)
							c.logger.Warn(errMsg)
						} else if len(deployments) > 0 {
							resourceContext.LastDeployment = &deployments[0]
						}
					}

					// Get recent commits
					sinceTime := time.Now().Add(-24 * time.Hour) // Last 24 hours
					commits, err := c.gitlabClient.FindRecentChanges(
						ctx,
						fmt.Sprintf("%d", project.ID),
						sinceTime,
					)
					if err != nil {
						errMsg := fmt.Sprintf("Failed to find recent changes: %v", err)
						errors = append(errors, errMsg)
						c.logger.Warn(errMsg)
					} else {
						// Here we'll limit to recent commits (last 5)...
						if len(commits) > 5 {
							commits = commits[:5]
						}
						resourceContext.RecentCommits = commits
					}
				}
			}
		}
	}

	// Collect any errors that occurred during correlation
	resourceContext.Errors = errors

	c.logger.Info("Resource deployment traced",
		"kind", kind,
		"name", name,
		"namespace", namespace,
		"argoApp", resourceContext.ArgoApplication != nil,
		"gitlabProject", resourceContext.GitLabProject != nil,
		"errors", len(errors))

	return resourceContext, nil
}

// isFileInAppSourcePath checks if a file is in the application's source path
func isFileInAppSourcePath(app models.ArgoApplication, file string) bool {
	sourcePath := app.Spec.Source.Path
	if sourcePath == "" {
		// If no specific path is provided, any file could affect the app
		return true
	}

	return strings.HasPrefix(file, sourcePath)
}

// hasHelmChanges checks if any of the changed files are related to Helm charts
func hasHelmChanges(diffs []models.GitLabDiff) bool {
	for _, diff := range diffs {
		path := diff.NewPath

		if strings.Contains(path, "Chart.yaml") ||
			strings.Contains(path, "values.yaml") ||
			(strings.Contains(path, "templates/") && strings.HasSuffix(path, ".yaml")) {
			return true
		}
	}

	return false
}

// appContainsAnyResource checks if an ArgoCD application contains any of the specified resources
func appContainsAnyResource(ctx context.Context, argoClient *argocd.Client, app models.ArgoApplication, resources []string) bool {
	tree, err := argoClient.GetResourceTree(ctx, app.Name)
	if err != nil {
		return false
	}

	for _, resource := range resources {
		parts := strings.Split(resource, "/")

		if len(parts) == 2 {
			// Format: Kind/Name
			kind := parts[0]
			name := parts[1]

			for _, node := range tree.Nodes {
				if strings.EqualFold(node.Kind, kind) && node.Name == name {
					return true
				}
			}
		} else if len(parts) == 3 {
			// Format: Namespace/Kind/Name
			namespace := parts[0]
			kind := parts[1]
			name := parts[2]

			for _, node := range tree.Nodes {
				if strings.EqualFold(node.Kind, kind) && node.Name == name && node.Namespace == namespace {
					return true
				}
			}
		}
	}

	return false
}

// FindResourcesAffectedByCommit finds resources affected by a specific Git commit
func (c *GitOpsCorrelator) FindResourcesAffectedByCommit(
	ctx context.Context,
	projectID string,
	commitSHA string,
) ([]models.ResourceContext, error) {
	c.logger.Info("Finding resources affected by commit", "projectID", projectID, "commitSHA", commitSHA)

	var result []models.ResourceContext

	// Get commit information from GitLab
	commit, err := c.gitlabClient.GetCommit(ctx, projectID, commitSHA)
	if err != nil {
		return nil, fmt.Errorf("failed to get commit: %w", err)
	}
	c.logger.Info("Processing commit", "author", commit.AuthorName, "message", commit.Title)

	// Get commit diff to see what files were changed
	diffs, err := c.gitlabClient.GetCommitDiff(ctx, projectID, commitSHA)
	if err != nil {
		return nil, fmt.Errorf("failed to get commit diff: %w", err)
	}

	// Get all ArgoCD applications
	argoApps, err := c.argoClient.ListApplications(ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to list ArgoCD applications: %w", err)
	}

	// Find applications that use this GitLab project as source
	projectPath := fmt.Sprintf("%s", projectID) // This might need more parsing depending on projectID format
	project, err := c.gitlabClient.GetProject(ctx, projectID)
	if err == nil && project != nil {
		projectPath = project.PathWithNamespace
	}

	// For each application, check if it's affected by the changed files
	for _, app := range argoApps {
		if !isAppSourcedFromProject(app, projectPath) {
			continue
		}

		// Check if the commit affects files used by this application
		if isAppAffectedByDiffs(app, diffs) {
			c.logger.Info("Found affected ArgoCD application", "app", app.Name)

			// Get resources managed by this application
			tree, err := c.argoClient.GetResourceTree(ctx, app.Name)
			if err != nil {
				c.logger.Warn("Failed to get resource tree", "app", app.Name, "error", err)
				continue
			}

			// For each resource in the app, create a deployment info object
			for _, node := range tree.Nodes {
				// Skip non-Kubernetes resources or resources with no name/namespace
				if node.Kind == "" || node.Name == "" {
					continue
				}

				// Avoid unnecessary duplicates in the result
				if isResourceAlreadyInResults(result, node.Kind, node.Name, node.Namespace) {
					continue
				}

				// Trace the deployment for this resource
				resourceContext, err := c.TraceResourceDeployment(
					ctx,
					node.Namespace,
					node.Kind,
					node.Name,
				)
				if err != nil {
					c.logger.Warn("Failed to trace resource deployment",
						"kind", node.Kind,
						"name", node.Name,
						"namespace", node.Namespace,
						"error", err)
					continue
				}

				result = append(result, resourceContext)
			}
		}
	}

	c.logger.Info("Found resources affected by commit",
		"projectID", projectID,
		"commitSHA", commitSHA,
		"resourceCount", len(result))

	return result, nil
}

// Helper functions

// extractGitLabProjectPath extracts the GitLab project path from a repo URL
func extractGitLabProjectPath(repoURL string) string {
	// Handle different URL formats

	// Format: https://gitlab.com/namespace/project.git
	if strings.HasPrefix(repoURL, "https://") || strings.HasPrefix(repoURL, "http://") {
		parts := strings.Split(repoURL, "/")
		if len(parts) < 3 {
			return ""
		}

		// Remove ".git" suffix if present
		lastPart := parts[len(parts)-1]
		if strings.HasSuffix(lastPart, ".git") {
			parts[len(parts)-1] = lastPart[:len(lastPart)-4]
		}

		// Reconstruct path without protocol and domain
		domainIndex := 2 // After http:// or https://
		if len(parts) <= domainIndex+1 {
			return ""
		}

		return strings.Join(parts[domainIndex+1:], "/")
	}

	// Format: [email protected]:namespace/project.git
	if strings.HasPrefix(repoURL, "git@") {
		// Split at ":" to get the path part
		parts := strings.Split(repoURL, ":")
		if len(parts) != 2 {
			return ""
		}

		// Remove ".git" suffix if present
		pathPart := parts[1]
		if strings.HasSuffix(pathPart, ".git") {
			pathPart = pathPart[:len(pathPart)-4]
		}

		return pathPart
	}

	return ""
}

// extractEnvironmentFromArgoApp tries to determine the environment from an ArgoCD application
func extractEnvironmentFromArgoApp(app models.ArgoApplication) string {
	// Check for environment in labels
	if env, ok := app.Metadata.Labels["environment"]; ok {
		return env
	}
	if env, ok := app.Metadata.Labels["env"]; ok {
		return env
	}

	// Check if environment is in the destination namespace
	if strings.Contains(app.Spec.Destination.Namespace, "prod") {
		return "production"
	}
	if strings.Contains(app.Spec.Destination.Namespace, "staging") {
		return "staging"
	}
	if strings.Contains(app.Spec.Destination.Namespace, "dev") {
		return "development"
	}

	// Check path in source for environment indicators
	if app.Spec.Source.Path != "" {
		if strings.Contains(app.Spec.Source.Path, "prod") {
			return "production"
		}
		if strings.Contains(app.Spec.Source.Path, "staging") {
			return "staging"
		}
		if strings.Contains(app.Spec.Source.Path, "dev") {
			return "development"
		}
	}

	// Default to destination namespace as a fallback
	return app.Spec.Destination.Namespace
}

// isAppSourcedFromProject checks if an ArgoCD application uses a specific GitLab project
func isAppSourcedFromProject(app models.ArgoApplication, projectPath string) bool {
	// Extract project path from app's repo URL
	appProjectPath := extractGitLabProjectPath(app.Spec.Source.RepoURL)

	// Compare paths
	return strings.EqualFold(appProjectPath, projectPath)
}

// isAppAffectedByDiffs checks if application manifests are affected by file changes
func isAppAffectedByDiffs(app models.ArgoApplication, diffs []models.GitLabDiff) bool {
	sourcePath := app.Spec.Source.Path
	if sourcePath == "" {
		// If no specific path is provided, any change could affect the app
		return true
	}

	// Check if any changed file is in the application's source path
	for _, diff := range diffs {
		if strings.HasPrefix(diff.NewPath, sourcePath) || strings.HasPrefix(diff.OldPath, sourcePath) {
			return true
		}
	}

	return false
}

// isResourceAlreadyInResults checks if a resource is already in the results list
func isResourceAlreadyInResults(results []models.ResourceContext, kind, name, namespace string) bool {
	for _, rc := range results {
		if rc.Kind == kind && rc.Name == name && rc.Namespace == namespace {
			return true
		}
	}
	return false
}

```

--------------------------------------------------------------------------------
/kubernetes-claude-mcp/internal/k8s/resource_mapper.go:
--------------------------------------------------------------------------------

```go
package k8s

import (
	"context"
	"fmt"
	"strings"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime/schema"
	
	"github.com/Blankcut/kubernetes-mcp-server/kubernetes-claude-mcp/pkg/logging"
)

// ResourceMapper maps relationships between Kubernetes resources
type ResourceMapper struct {
	client *Client
	logger *logging.Logger
}

// ResourceRelationship represents a relationship between two resources
type ResourceRelationship struct {
	SourceKind      string `json:"sourceKind"`
	SourceName      string `json:"sourceName"`
	SourceNamespace string `json:"sourceNamespace"`
	TargetKind      string `json:"targetKind"`
	TargetName      string `json:"targetName"`
	TargetNamespace string `json:"targetNamespace"`
	RelationType    string `json:"relationType"`
}

// NamespaceTopology represents the topology of resources in a namespace
type NamespaceTopology struct {
	Namespace    string                         `json:"namespace"`
	Resources    map[string][]string            `json:"resources"`
	Relationships []ResourceRelationship        `json:"relationships"`
	Metrics      map[string]map[string]int      `json:"metrics"`
	Health       map[string]map[string]string   `json:"health"`
}

// NewResourceMapper creates a new resource mapper
func NewResourceMapper(client *Client) *ResourceMapper {
	return &ResourceMapper{
		client: client,
		logger: client.logger.Named("resource-mapper"),
	}
}

// GetNamespaceTopology maps all resources and their relationships in a namespace
func (m *ResourceMapper) GetNamespaceTopology(ctx context.Context, namespace string) (*NamespaceTopology, error) {
	m.logger.Info("Mapping namespace topology", "namespace", namespace)
	
	// Initialize topology
	topology := &NamespaceTopology{
		Namespace:    namespace,
		Resources:    make(map[string][]string),
		Relationships: []ResourceRelationship{},
		Metrics:      make(map[string]map[string]int),
		Health:       make(map[string]map[string]string),
	}

	// Discover all available resource types
	resources, err := m.client.discoveryClient.ServerPreferredResources()
	if err != nil {
		return nil, fmt.Errorf("failed to get server resources: %w", err)
	}

	// Collect all namespaced resources
	for _, resourceList := range resources {
		gv, err := schema.ParseGroupVersion(resourceList.GroupVersion)
		if err != nil {
			m.logger.Warn("Failed to parse group version", "groupVersion", resourceList.GroupVersion)
			continue
		}

		for _, r := range resourceList.APIResources {
			// Skip resources that can't be listed or aren't namespaced
			if !strings.Contains(r.Verbs.String(), "list") || !r.Namespaced {
				continue
			}

			// Build GVR for this resource type
			gvr := schema.GroupVersionResource{
				Group:    gv.Group,
				Version:  gv.Version,
				Resource: r.Name,
			}

			// List resources of this type
			m.logger.Debug("Listing resources", "namespace", namespace, "resource", r.Name)
			list, err := m.client.dynamicClient.Resource(gvr).Namespace(namespace).List(ctx, metav1.ListOptions{})
			if err != nil {
				m.logger.Warn("Failed to list resources", 
					"namespace", namespace, 
					"resource", r.Name, 
					"error", err)
				continue
			}

			// Add to topology
			if len(list.Items) > 0 {
				topology.Resources[r.Kind] = make([]string, len(list.Items))
				topology.Metrics[r.Kind] = map[string]int{"count": len(list.Items)}
				topology.Health[r.Kind] = make(map[string]string)
				
				for i, item := range list.Items {
					topology.Resources[r.Kind][i] = item.GetName()
					
					// Determine health status
					health := m.determineResourceHealth(&item)
					topology.Health[r.Kind][item.GetName()] = health
				}
				
				// Find relationships for this resource type
				relationships := m.findRelationships(ctx, list.Items, namespace)
				topology.Relationships = append(topology.Relationships, relationships...)
			}
		}
	}

	m.logger.Info("Namespace topology mapped", 
		"namespace", namespace, 
		"resourceTypes", len(topology.Resources),
		"relationships", len(topology.Relationships))
	
	return topology, nil
}

// GetResourceGraph returns a resource graph for visualization
func (m *ResourceMapper) GetResourceGraph(ctx context.Context, namespace string) (map[string]interface{}, error) {
	topology, err := m.GetNamespaceTopology(ctx, namespace)
	if err != nil {
		return nil, err
	}
	
	// Convert topology to graph format
	graph := map[string]interface{}{
		"nodes": []map[string]interface{}{},
		"edges": []map[string]interface{}{},
	}
	
	// Add nodes
	nodeIndex := make(map[string]int)
	nodeCount := 0
	
	for kind, resources := range topology.Resources {
		for _, name := range resources {
			health := "unknown"
			if h, ok := topology.Health[kind][name]; ok {
				health = h
			}
			
			node := map[string]interface{}{
				"id":       nodeCount,
				"kind":     kind,
				"name":     name,
				"health":   health,
				"group":    kind,
			}
			
			// Add to nodes array
			graph["nodes"] = append(graph["nodes"].([]map[string]interface{}), node)
			
			// Save index for edge creation
			nodeIndex[fmt.Sprintf("%s/%s", kind, name)] = nodeCount
			nodeCount++
		}
	}
	
	// Add edges
	for _, rel := range topology.Relationships {
		sourceKey := fmt.Sprintf("%s/%s", rel.SourceKind, rel.SourceName)
		targetKey := fmt.Sprintf("%s/%s", rel.TargetKind, rel.TargetName)
		
		sourceIdx, sourceOk := nodeIndex[sourceKey]
		targetIdx, targetOk := nodeIndex[targetKey]
		
		if sourceOk && targetOk {
			edge := map[string]interface{}{
				"source":      sourceIdx,
				"target":      targetIdx,
				"relationship": rel.RelationType,
			}
			
			graph["edges"] = append(graph["edges"].([]map[string]interface{}), edge)
		}
	}
	
	return graph, nil
}

// findRelationships discovers relationships between resources
func (m *ResourceMapper) findRelationships(ctx context.Context, resources []unstructured.Unstructured, namespace string) []ResourceRelationship {
	var relationships []ResourceRelationship
	
	for _, resource := range resources {
		// Check owner references
		for _, ownerRef := range resource.GetOwnerReferences() {
			rel := ResourceRelationship{
				SourceKind:      ownerRef.Kind,
				SourceName:      ownerRef.Name,
				SourceNamespace: namespace,
				TargetKind:      resource.GetKind(),
				TargetName:      resource.GetName(),
				TargetNamespace: namespace,
				RelationType:    "owns",
			}
			relationships = append(relationships, rel)
		}
		
		// Check for Pod -> Service relationships (via labels/selectors)
		if resource.GetKind() == "Service" {
			selector, found, _ := unstructured.NestedMap(resource.Object, "spec", "selector")
			if found && len(selector) > 0 {
				// Find pods matching this selector
				pods, err := m.client.clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{
					LabelSelector: m.labelsToSelector(selector),
				})
				
				if err == nil {
					for _, pod := range pods.Items {
						rel := ResourceRelationship{
							SourceKind:      "Service",
							SourceName:      resource.GetName(),
							SourceNamespace: namespace,
							TargetKind:      "Pod",
							TargetName:      pod.Name,
							TargetNamespace: namespace,
							RelationType:    "selects",
						}
						relationships = append(relationships, rel)
					}
				}
			}
		}
		
		// Check for ConfigMap/Secret references in Pods
		if resource.GetKind() == "Pod" {
			// Check volumes for ConfigMap references
			volumes, found, _ := unstructured.NestedSlice(resource.Object, "spec", "volumes")
			if found {
				for _, v := range volumes {
					volume, ok := v.(map[string]interface{})
					if !ok {
						continue
					}
					
					// Check for ConfigMap references
					if configMap, hasConfigMap, _ := unstructured.NestedMap(volume, "configMap"); hasConfigMap {
						if cmName, hasName, _ := unstructured.NestedString(configMap, "name"); hasName {
							rel := ResourceRelationship{
								SourceKind:      "Pod",
								SourceName:      resource.GetName(),
								SourceNamespace: namespace,
								TargetKind:      "ConfigMap",
								TargetName:      cmName,
								TargetNamespace: namespace,
								RelationType:    "mounts",
							}
							relationships = append(relationships, rel)
						}
					}
					
					// Check for Secret references
					if secret, hasSecret, _ := unstructured.NestedMap(volume, "secret"); hasSecret {
						if secretName, hasName, _ := unstructured.NestedString(secret, "secretName"); hasName {
							rel := ResourceRelationship{
								SourceKind:      "Pod",
								SourceName:      resource.GetName(),
								SourceNamespace: namespace,
								TargetKind:      "Secret",
								TargetName:      secretName,
								TargetNamespace: namespace,
								RelationType:    "mounts",
							}
							relationships = append(relationships, rel)
						}
					}
				}
			}
			
			// Check environment variables for ConfigMap/Secret references
			containers, found, _ := unstructured.NestedSlice(resource.Object, "spec", "containers")
			if found {
				for _, c := range containers {
					container, ok := c.(map[string]interface{})
					if !ok {
						continue
					}
					
					// Check for EnvFrom references
					envFrom, hasEnvFrom, _ := unstructured.NestedSlice(container, "envFrom")
					if hasEnvFrom {
						for _, ef := range envFrom {
							envFromObj, ok := ef.(map[string]interface{})
							if !ok {
								continue
							}
							
							// Check for ConfigMap references
							if configMap, hasConfigMap, _ := unstructured.NestedMap(envFromObj, "configMapRef"); hasConfigMap {
								if cmName, hasName, _ := unstructured.NestedString(configMap, "name"); hasName {
									rel := ResourceRelationship{
										SourceKind:      "Pod",
										SourceName:      resource.GetName(),
										SourceNamespace: namespace,
										TargetKind:      "ConfigMap",
										TargetName:      cmName,
										TargetNamespace: namespace,
										RelationType:    "configures",
									}
									relationships = append(relationships, rel)
								}
							}
							
							// Check for Secret references
							if secret, hasSecret, _ := unstructured.NestedMap(envFromObj, "secretRef"); hasSecret {
								if secretName, hasName, _ := unstructured.NestedString(secret, "name"); hasName {
									rel := ResourceRelationship{
										SourceKind:      "Pod",
										SourceName:      resource.GetName(),
										SourceNamespace: namespace,
										TargetKind:      "Secret",
										TargetName:      secretName,
										TargetNamespace: namespace,
										RelationType:    "configures",
									}
									relationships = append(relationships, rel)
								}
							}
						}
					}
					
					// Check individual env vars for ConfigMap/Secret references
					env, hasEnv, _ := unstructured.NestedSlice(container, "env")
					if hasEnv {
						for _, e := range env {
							envVar, ok := e.(map[string]interface{})
							if !ok {
								continue
							}
							
							// Check for ConfigMap references
							if valueFrom, hasValueFrom, _ := unstructured.NestedMap(envVar, "valueFrom"); hasValueFrom {
								if configMap, hasConfigMap, _ := unstructured.NestedMap(valueFrom, "configMapKeyRef"); hasConfigMap {
									if cmName, hasName, _ := unstructured.NestedString(configMap, "name"); hasName {
										rel := ResourceRelationship{
											SourceKind:      "Pod",
											SourceName:      resource.GetName(),
											SourceNamespace: namespace,
											TargetKind:      "ConfigMap",
											TargetName:      cmName,
											TargetNamespace: namespace,
											RelationType:    "configures",
										}
										relationships = append(relationships, rel)
									}
								}
								
								// Check for Secret references
								if secret, hasSecret, _ := unstructured.NestedMap(valueFrom, "secretKeyRef"); hasSecret {
									if secretName, hasName, _ := unstructured.NestedString(secret, "name"); hasName {
										rel := ResourceRelationship{
											SourceKind:      "Pod",
											SourceName:      resource.GetName(),
											SourceNamespace: namespace,
											TargetKind:      "Secret",
											TargetName:      secretName,
											TargetNamespace: namespace,
											RelationType:    "configures",
										}
										relationships = append(relationships, rel)
									}
								}
							}
						}
					}
				}
			}
		}
		
		// Check for PVC -> PV relationships
		if resource.GetKind() == "PersistentVolumeClaim" {
			volumeName, found, _ := unstructured.NestedString(resource.Object, "spec", "volumeName")
			if found && volumeName != "" {
				rel := ResourceRelationship{
					SourceKind:      "PersistentVolumeClaim",
					SourceName:      resource.GetName(),
					SourceNamespace: namespace,
					TargetKind:      "PersistentVolume",
					TargetName:      volumeName,
					TargetNamespace: "",
					RelationType:    "binds",
				}
				relationships = append(relationships, rel)
			}
		}
		
		// Check for Ingress -> Service relationships
		if resource.GetKind() == "Ingress" {
			rules, found, _ := unstructured.NestedSlice(resource.Object, "spec", "rules")
			if found {
				for _, r := range rules {
					rule, ok := r.(map[string]interface{})
					if !ok {
						continue
					}
					
					http, found, _ := unstructured.NestedMap(rule, "http")
					if !found {
						continue
					}
					
					paths, found, _ := unstructured.NestedSlice(http, "paths")
					if !found {
						continue
					}
					
					for _, p := range paths {
						path, ok := p.(map[string]interface{})
						if !ok {
							continue
						}
						
						backend, found, _ := unstructured.NestedMap(path, "backend")
						if !found {
							// Check for newer API version format
							backend, found, _ = unstructured.NestedMap(path, "backend", "service")
							if !found {
								continue
							}
						}
						
						serviceName, found, _ := unstructured.NestedString(backend, "name")
						if found {
							rel := ResourceRelationship{
								SourceKind:      "Ingress",
								SourceName:      resource.GetName(),
								SourceNamespace: namespace,
								TargetKind:      "Service",
								TargetName:      serviceName,
								TargetNamespace: namespace,
								RelationType:    "routes",
							}
							relationships = append(relationships, rel)
						}
					}
				}
			}
		}
	}
	
	// Deduplicate relationships
	deduplicatedRelationships := make([]ResourceRelationship, 0)
	relMap := make(map[string]bool)
	
	for _, rel := range relationships {
		key := fmt.Sprintf("%s/%s/%s/%s/%s/%s/%s", 
			rel.SourceKind, rel.SourceName, rel.SourceNamespace,
			rel.TargetKind, rel.TargetName, rel.TargetNamespace,
			rel.RelationType)
			
		if _, exists := relMap[key]; !exists {
			relMap[key] = true
			deduplicatedRelationships = append(deduplicatedRelationships, rel)
		}
	}
	
	return deduplicatedRelationships
}

// labelsToSelector converts a map of labels to a selector string
func (m *ResourceMapper) labelsToSelector(labels map[string]interface{}) string {
	var selectors []string
	
	for key, value := range labels {
		if strValue, ok := value.(string); ok {
			selectors = append(selectors, fmt.Sprintf("%s=%s", key, strValue))
		}
	}
	
	return strings.Join(selectors, ",")
}

// determineResourceHealth determines the health status of a resource
func (m *ResourceMapper) determineResourceHealth(obj *unstructured.Unstructured) string {
	kind := obj.GetKind()
	
	// Check common status fields
	status, found, _ := unstructured.NestedMap(obj.Object, "status")
	if !found {
		return "unknown"
	}
	
	// Check different resource types
	switch kind {
	case "Pod":
		phase, found, _ := unstructured.NestedString(status, "phase")
		if found {
			switch phase {
			case "Running", "Succeeded":
				return "healthy"
			case "Pending":
				return "progressing"
			case "Failed":
				return "unhealthy"
			default:
				return "unknown"
			}
		}
		
	case "Deployment", "StatefulSet", "DaemonSet", "ReplicaSet":
		// Check if all replicas are available
		replicas, foundReplicas, _ := unstructured.NestedInt64(obj.Object, "spec", "replicas")
		if !foundReplicas {
			replicas = 1 // Default to 1 if not specified
		}
		
		availableReplicas, foundAvailable, _ := unstructured.NestedInt64(status, "availableReplicas")
		if foundAvailable && availableReplicas == replicas {
			return "healthy"
		} else if foundAvailable && availableReplicas > 0 {
			return "progressing"
		} else {
			return "unhealthy"
		}
		
	case "Service":
		// Services are typically healthy unless they have no endpoints
		// We'd need to check endpoints separately
		return "healthy"
		
	case "Ingress":
		// Check if LoadBalancer has assigned addresses
		ingress, found, _ := unstructured.NestedSlice(status, "loadBalancer", "ingress")
		if found && len(ingress) > 0 {
			return "healthy"
		}
		return "progressing"
		
	case "PersistentVolumeClaim":
		phase, found, _ := unstructured.NestedString(status, "phase")
		if found && phase == "Bound" {
			return "healthy"
		} else if found && phase == "Pending" {
			return "progressing"
		} else {
			return "unhealthy"
		}
	
	case "Job":
		conditions, found, _ := unstructured.NestedSlice(status, "conditions")
		if found {
			for _, c := range conditions {
				condition, ok := c.(map[string]interface{})
				if !ok {
					continue
				}
				
				condType, typeFound, _ := unstructured.NestedString(condition, "type")
				condStatus, statusFound, _ := unstructured.NestedString(condition, "status")
				
				if typeFound && statusFound && condType == "Complete" && condStatus == "True" {
					return "healthy"
				} else if typeFound && statusFound && condType == "Failed" && condStatus == "True" {
					return "unhealthy"
				}
			}
			return "progressing"
		}
		
	default:
		// For other resources, try to check common status conditions
		conditions, found, _ := unstructured.NestedSlice(status, "conditions")
		if found {
			for _, c := range conditions {
				condition, ok := c.(map[string]interface{})
				if !ok {
					continue
				}
				
				condType, typeFound, _ := unstructured.NestedString(condition, "type")
				condStatus, statusFound, _ := unstructured.NestedString(condition, "status")
				
				if typeFound && statusFound {
					// Check for common condition types indicating health
					if (condType == "Ready" || condType == "Available") && condStatus == "True" {
						return "healthy"
					} else if condType == "Progressing" && condStatus == "True" {
						return "progressing"
					} else if (condType == "Failed" || condType == "Error") && condStatus == "True" {
						return "unhealthy"
					}
				}
			}
		}
	}
	
	return "unknown"
}
```

--------------------------------------------------------------------------------
/kubernetes-claude-mcp/internal/correlator/troubleshoot.go:
--------------------------------------------------------------------------------

```go
package correlator

import (
	"context"
	"fmt"
	"strings"

	"github.com/Blankcut/kubernetes-mcp-server/kubernetes-claude-mcp/internal/k8s"
	"github.com/Blankcut/kubernetes-mcp-server/kubernetes-claude-mcp/internal/models"
	"github.com/Blankcut/kubernetes-mcp-server/kubernetes-claude-mcp/pkg/logging"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

// TroubleshootCorrelator provides specialized logic for troubleshooting
type TroubleshootCorrelator struct {
	gitOpsCorrelator *GitOpsCorrelator
	k8sClient        *k8s.Client
	logger           *logging.Logger
}

// NewTroubleshootCorrelator creates a new troubleshooting correlator
func NewTroubleshootCorrelator(gitOpsCorrelator *GitOpsCorrelator, k8sClient *k8s.Client, logger *logging.Logger) *TroubleshootCorrelator {
	if logger == nil {
		logger = logging.NewLogger().Named("troubleshoot")
	}
	
	return &TroubleshootCorrelator{
		gitOpsCorrelator: gitOpsCorrelator,
		k8sClient:        k8sClient,
		logger:           logger,
	}
}

// TroubleshootResource analyzes a resource for common issues
func (tc *TroubleshootCorrelator) TroubleshootResource(ctx context.Context, namespace, kind, name string) (*models.TroubleshootResult, error) {
	tc.logger.Info("Troubleshooting resource", "kind", kind, "name", name, "namespace", namespace)
	
	// First, trace the resource deployment
	resourceContext, err := tc.gitOpsCorrelator.TraceResourceDeployment(ctx, namespace, kind, name)
	if err != nil {
		return nil, fmt.Errorf("failed to trace resource deployment: %w", err)
	}
	
	// Get the raw resource for detailed analysis
	resource, err := tc.k8sClient.GetResource(ctx, kind, namespace, name)
	if err != nil {
		tc.logger.Warn("Failed to get resource for detailed analysis", "error", err)
	}
	
	// Initialize troubleshooting result
	result := &models.TroubleshootResult{
		ResourceContext: resourceContext,
		Issues:          []models.Issue{},
		Recommendations: []string{},
	}
	
	// Analyze Kubernetes events for issues
	tc.analyzeKubernetesEvents(resourceContext, result)
	
	// Analyze resource status and conditions if resource was retrieved
	if resource != nil {
		// Pod-specific analysis
		if strings.EqualFold(kind, "pod") {
			tc.analyzePodStatus(ctx, resource, result)
		}
		
		// Deployment-specific analysis
		if strings.EqualFold(kind, "deployment") {
			tc.analyzeDeploymentStatus(resource, result)
		}
	}
	
	// Analyze ArgoCD sync status
	tc.analyzeArgoStatus(resourceContext, result)
	
	// Analyze GitLab pipeline status
	tc.analyzeGitLabStatus(resourceContext, result)
	
	// Check if resource is healthy
	if len(result.Issues) == 0 && resource != nil && !tc.isResourceHealthy(resource) {
		issue := models.Issue{
			Source:      "Kubernetes",
			Category:    "UnknownIssue",
			Severity:    "Warning",
			Title:       "Resource Not Healthy",
			Description: fmt.Sprintf("%s %s/%s is not in a healthy state", kind, namespace, name),
		}
		result.Issues = append(result.Issues, issue)
	}
	
	// Generate recommendations based on issues
	tc.generateRecommendations(result)
	
	tc.logger.Info("Troubleshooting completed", 
		"kind", kind, 
		"name", name, 
		"namespace", namespace,
		"issueCount", len(result.Issues),
		"recommendationCount", len(result.Recommendations))
	
	return result, nil
}

// isResourceHealthy checks if a resource is in a healthy state
func (tc *TroubleshootCorrelator) isResourceHealthy(resource *unstructured.Unstructured) bool {
	kind := resource.GetKind()
	
	// Pod health check
	if strings.EqualFold(kind, "pod") {
		phase, found, _ := unstructured.NestedString(resource.Object, "status", "phase")
		return found && phase == "Running"
	}
	
	// Deployment health check
	if strings.EqualFold(kind, "deployment") {
		// Check if available replicas match desired replicas
		desiredReplicas, found1, _ := unstructured.NestedInt64(resource.Object, "spec", "replicas")
		availableReplicas, found2, _ := unstructured.NestedInt64(resource.Object, "status", "availableReplicas")
		return found1 && found2 && desiredReplicas == availableReplicas && availableReplicas > 0
	}
	
	// Default: assume healthy
	return true
}

// analyzeDeploymentStatus analyzes deployment-specific status
func (tc *TroubleshootCorrelator) analyzeDeploymentStatus(deployment *unstructured.Unstructured, result *models.TroubleshootResult) {
	// Check if deployment is ready
	desiredReplicas, found1, _ := unstructured.NestedInt64(deployment.Object, "spec", "replicas")
	availableReplicas, found2, _ := unstructured.NestedInt64(deployment.Object, "status", "availableReplicas")
	readyReplicas, found3, _ := unstructured.NestedInt64(deployment.Object, "status", "readyReplicas")
	
	if !found1 || !found2 || availableReplicas < desiredReplicas {
		issue := models.Issue{
			Source:      "Kubernetes",
			Category:    "DeploymentNotAvailable",
			Severity:    "Warning",
			Title:       "Deployment Not Fully Available",
			Description: fmt.Sprintf("Deployment has %d/%d available replicas", availableReplicas, desiredReplicas),
		}
		result.Issues = append(result.Issues, issue)
	}
	
	if !found1 || !found3 || readyReplicas < desiredReplicas {
		issue := models.Issue{
			Source:      "Kubernetes",
			Category:    "DeploymentNotReady",
			Severity:    "Warning",
			Title:       "Deployment Not Fully Ready",
			Description: fmt.Sprintf("Deployment has %d/%d ready replicas", readyReplicas, desiredReplicas),
		}
		result.Issues = append(result.Issues, issue)
	}
	
	// Check deployment conditions
	conditions, found, _ := unstructured.NestedSlice(deployment.Object, "status", "conditions")
	if found {
		for _, c := range conditions {
			condition, ok := c.(map[string]interface{})
			if !ok {
				continue
			}
			
			conditionType, _, _ := unstructured.NestedString(condition, "type")
			status, _, _ := unstructured.NestedString(condition, "status")
			reason, _, _ := unstructured.NestedString(condition, "reason")
			message, _, _ := unstructured.NestedString(condition, "message")
			
			if conditionType == "Available" && status != "True" {
				issue := models.Issue{
					Source:      "Kubernetes",
					Category:    "DeploymentNotAvailable",
					Severity:    "Warning",
					Title:       "Deployment Not Available",
					Description: fmt.Sprintf("Deployment availability issue: %s - %s", reason, message),
				}
				result.Issues = append(result.Issues, issue)
			}
			
			if conditionType == "Progressing" && status != "True" {
				issue := models.Issue{
					Source:      "Kubernetes",
					Category:    "DeploymentNotProgressing",
					Severity:    "Warning",
					Title:       "Deployment Not Progressing",
					Description: fmt.Sprintf("Deployment progress issue: %s - %s", reason, message),
				}
				result.Issues = append(result.Issues, issue)
			}
		}
	}
}

// analyzePodStatus analyzes pod-specific status information
func (tc *TroubleshootCorrelator) analyzePodStatus(ctx context.Context, pod *unstructured.Unstructured, result *models.TroubleshootResult) {
	// Check pod phase
	phase, found, _ := unstructured.NestedString(pod.Object, "status", "phase")
	if found && phase != "Running" && phase != "Succeeded" {
		issue := models.Issue{
			Source:      "Kubernetes",
			Category:    "PodNotRunning",
			Severity:    "Warning",
			Title:       "Pod Not Running",
			Description: fmt.Sprintf("Pod is in %s state", phase),
		}
		
		if phase == "Pending" {
			issue.Title = "Pod Pending"
			issue.Description = "Pod is still in Pending state and hasn't started running"
		} else if phase == "Failed" {
			issue.Severity = "Error"
			issue.Title = "Pod Failed"
		}
		
		result.Issues = append(result.Issues, issue)
	}
	
	// Check pod conditions
	conditions, found, _ := unstructured.NestedSlice(pod.Object, "status", "conditions")
	if found {
		for _, c := range conditions {
			condition, ok := c.(map[string]interface{})
			if !ok {
				continue
			}
			
			conditionType, _, _ := unstructured.NestedString(condition, "type")
			status, _, _ := unstructured.NestedString(condition, "status")
			
			if conditionType == "PodScheduled" && status != "True" {
				issue := models.Issue{
					Source:      "Kubernetes",
					Category:    "SchedulingIssue",
					Severity:    "Warning",
					Title:       "Pod Scheduling Issue",
					Description: "Pod cannot be scheduled onto a node",
				}
				result.Issues = append(result.Issues, issue)
			}
			
			if conditionType == "Initialized" && status != "True" {
				issue := models.Issue{
					Source:      "Kubernetes",
					Category:    "InitializationIssue",
					Severity:    "Warning",
					Title:       "Pod Initialization Issue",
					Description: "Pod initialization containers have not completed successfully",
				}
				result.Issues = append(result.Issues, issue)
			}
			
			if conditionType == "ContainersReady" && status != "True" {
				issue := models.Issue{
					Source:      "Kubernetes",
					Category:    "ContainerReadinessIssue",
					Severity:    "Warning",
					Title:       "Container Readiness Issue",
					Description: "One or more containers are not ready",
				}
				result.Issues = append(result.Issues, issue)
			}
			
			if conditionType == "Ready" && status != "True" {
				issue := models.Issue{
					Source:      "Kubernetes",
					Category:    "PodNotReady",
					Severity:    "Warning",
					Title:       "Pod Not Ready",
					Description: "Pod is not ready to serve traffic",
				}
				result.Issues = append(result.Issues, issue)
			}
		}
	}
	
	// Check container statuses
	containerStatuses, found, _ := unstructured.NestedSlice(pod.Object, "status", "containerStatuses")
	if found {
		tc.analyzeContainerStatuses(containerStatuses, false, result)
	}
	
	// Check init container statuses if they exist
	initContainerStatuses, found, _ := unstructured.NestedSlice(pod.Object, "status", "initContainerStatuses")
	if found {
		tc.analyzeContainerStatuses(initContainerStatuses, true, result)
	}
	
	// Check for volume issues
	volumes, found, _ := unstructured.NestedSlice(pod.Object, "spec", "volumes")
	if found {
		// Track PVC usage
		pvcVolumes := []string{}
		
		for _, v := range volumes {
			volume, ok := v.(map[string]interface{})
			if !ok {
				continue
			}
						
			// Check for PVC volumes
			pvc, pvcFound, _ := unstructured.NestedMap(volume, "persistentVolumeClaim")
			if pvcFound && pvc != nil {
				claimName, nameFound, _ := unstructured.NestedString(pvc, "claimName")
				if nameFound && claimName != "" {
					pvcVolumes = append(pvcVolumes, claimName)
				}
			}
		}
		
		// If PVC volumes found, check their status
		if len(pvcVolumes) > 0 {
			for _, pvcName := range pvcVolumes {
				pvc, err := tc.k8sClient.GetResource(ctx, "persistentvolumeclaim", pod.GetNamespace(), pvcName)
				if err != nil {
					issue := models.Issue{
						Source:      "Kubernetes",
						Category:    "VolumeIssue",
						Severity:    "Warning",
						Title:       "PVC Not Found",
						Description: fmt.Sprintf("PersistentVolumeClaim %s not found", pvcName),
					}
					result.Issues = append(result.Issues, issue)
					continue
				}
				
				phase, phaseFound, _ := unstructured.NestedString(pvc.Object, "status", "phase")
				if !phaseFound || phase != "Bound" {
					issue := models.Issue{
						Source:      "Kubernetes",
						Category:    "VolumeIssue",
						Severity:    "Warning",
						Title:       "PVC Not Bound",
						Description: fmt.Sprintf("PersistentVolumeClaim %s is in %s state", pvcName, phase),
					}
					result.Issues = append(result.Issues, issue)
				}
			}
		}
	}
}

// analyzeContainerStatuses analyzes container status information
func (tc *TroubleshootCorrelator) analyzeContainerStatuses(statuses []interface{}, isInit bool, result *models.TroubleshootResult) {
	containerType := "Container"
	if isInit {
		containerType = "Init Container"
	}
	
	for _, cs := range statuses {
		containerStatus, ok := cs.(map[string]interface{})
		if !ok {
			continue
		}
		
		containerName, _, _ := unstructured.NestedString(containerStatus, "name")
		ready, _, _ := unstructured.NestedBool(containerStatus, "ready")
		restartCount, _, _ := unstructured.NestedInt64(containerStatus, "restartCount")
		
		if !ready {
			// Check for specific container state
			state, stateExists, _ := unstructured.NestedMap(containerStatus, "state")
			if stateExists && state != nil {
				waitingState, waitingExists, _ := unstructured.NestedMap(state, "waiting")
				if waitingExists && waitingState != nil {
					reason, reasonFound, _ := unstructured.NestedString(waitingState, "reason")
					message, messageFound, _ := unstructured.NestedString(waitingState, "message")
					
					reasonStr := ""
					if reasonFound {
						reasonStr = reason
					}
					
					messageStr := ""
					if messageFound {
						messageStr = message
					}
					
					issue := models.Issue{
						Source:      "Kubernetes",
						Category:    "ContainerWaiting",
						Severity:    "Warning",
						Title:       fmt.Sprintf("%s %s Waiting", containerType, containerName),
						Description: fmt.Sprintf("%s is waiting: %s - %s", containerType, reasonStr, messageStr),
					}
					
					if reason == "CrashLoopBackOff" {
						issue.Category = "CrashLoopBackOff"
						issue.Severity = "Error"
						issue.Title = fmt.Sprintf("%s %s CrashLoopBackOff", containerType, containerName)
					} else if reason == "ImagePullBackOff" || reason == "ErrImagePull" {
						issue.Category = "ImagePullError"
						issue.Title = fmt.Sprintf("%s %s Image Pull Error", containerType, containerName)
					} else if reason == "PodInitializing" || reason == "ContainerCreating" {
						issue.Category = "PodInitializing"
						issue.Title = fmt.Sprintf("%s Still Initializing", containerType)
						issue.Description = fmt.Sprintf("%s is still being created or initialized", containerType)
					}
					
					result.Issues = append(result.Issues, issue)
				}
				
				terminatedState, terminatedExists, _ := unstructured.NestedMap(state, "terminated")
				if terminatedExists && terminatedState != nil {
					reason, reasonFound, _ := unstructured.NestedString(terminatedState, "reason")
					exitCode, exitCodeFound, _ := unstructured.NestedInt64(terminatedState, "exitCode")
					message, messageFound, _ := unstructured.NestedString(terminatedState, "message")
					
					reasonStr := ""
					if reasonFound {
						reasonStr = reason
					}
					
					messageStr := ""
					if messageFound {
						messageStr = message
					}
					
					var exitCodeVal int64 = 0
					if exitCodeFound {
						exitCodeVal = exitCode
					}
					
					if exitCodeVal != 0 {
						issue := models.Issue{
							Source:      "Kubernetes",
							Category:    "ContainerTerminated",
							Severity:    "Error",
							Title:       fmt.Sprintf("%s %s Terminated", containerType, containerName),
							Description: fmt.Sprintf("%s terminated with exit code %d: %s - %s", containerType, exitCodeVal, reasonStr, messageStr),
						}
						result.Issues = append(result.Issues, issue)
					}
				}
			}
		}
		
		if restartCount > 3 {
			issue := models.Issue{
				Source:      "Kubernetes",
				Category:    "FrequentRestarts",
				Severity:    "Warning",
				Title:       fmt.Sprintf("%s %s Frequent Restarts", containerType, containerName),
				Description: fmt.Sprintf("%s has restarted %d times", containerType, restartCount),
			}
			result.Issues = append(result.Issues, issue)
		}
	}
}

// analyzeKubernetesEvents looks for common issues in Kubernetes events
func (tc *TroubleshootCorrelator) analyzeKubernetesEvents(rc models.ResourceContext, result *models.TroubleshootResult) {
	for _, event := range rc.Events {
		// Look for error events
		if event.Type == "Warning" {
			issue := models.Issue{
				Source:      "Kubernetes",
				Severity:    "Warning",
				Description: fmt.Sprintf("%s: %s", event.Reason, event.Message),
			}
			
			// Categorize common issues
			switch {
			case strings.Contains(event.Reason, "Failed") && strings.Contains(event.Message, "ImagePull"):
				issue.Category = "ImagePullError"
				issue.Title = "Image Pull Failure"
			
			case strings.Contains(event.Reason, "Unhealthy"):
				issue.Category = "HealthCheckFailure"
				issue.Title = "Health Check Failure"
			
			case strings.Contains(event.Message, "memory"):
				issue.Category = "ResourceIssue"
				issue.Title = "Memory Resource Issue"
				
			case strings.Contains(event.Message, "cpu"):
				issue.Category = "ResourceIssue"
				issue.Title = "CPU Resource Issue"
				
			case strings.Contains(event.Reason, "BackOff"):
				issue.Category = "CrashLoopBackOff"
				issue.Title = "Container Crash Loop"
				
			default:
				issue.Category = "OtherWarning"
				issue.Title = "Kubernetes Warning"
			}
			
			result.Issues = append(result.Issues, issue)
		}
	}
}

// analyzeArgoStatus looks for issues in ArgoCD status
func (tc *TroubleshootCorrelator) analyzeArgoStatus(rc models.ResourceContext, result *models.TroubleshootResult) {
	if rc.ArgoApplication == nil {
		// No ArgoCD application managing this resource
		return
	}
	
	// Check sync status
	if rc.ArgoSyncStatus != "Synced" {
		issue := models.Issue{
			Source:      "ArgoCD",
			Category:    "SyncIssue",
			Severity:    "Warning",
			Title:       "ArgoCD Sync Issue",
			Description: fmt.Sprintf("Application %s is not synced (status: %s)", rc.ArgoApplication.Name, rc.ArgoSyncStatus),
		}
		result.Issues = append(result.Issues, issue)
	}
	
	// Check health status
	if rc.ArgoHealthStatus != "Healthy" {
		issue := models.Issue{
			Source:      "ArgoCD",
			Category:    "HealthIssue",
			Severity:    "Warning",
			Title:       "ArgoCD Health Issue",
			Description: fmt.Sprintf("Application %s is not healthy (status: %s)", rc.ArgoApplication.Name, rc.ArgoHealthStatus),
		}
		result.Issues = append(result.Issues, issue)
	}
	
	// Check for recent sync failures
	for _, history := range rc.ArgoSyncHistory {
		if history.Status == "Failed" {
			issue := models.Issue{
				Source:      "ArgoCD",
				Category:    "SyncFailure",
				Severity:    "Error",
				Title:       "Recent Sync Failure",
				Description: fmt.Sprintf("Sync at %s failed with revision %s", history.DeployedAt.Format("2006-01-02 15:04:05"), history.Revision),
			}
			result.Issues = append(result.Issues, issue)
			break // Only report the most recent failure
		}
	}
}

// analyzeGitLabStatus looks for issues in GitLab pipelines and deployments
func (tc *TroubleshootCorrelator) analyzeGitLabStatus(rc models.ResourceContext, result *models.TroubleshootResult) {
	if rc.GitLabProject == nil {
		// No GitLab project information
		return
	}
	
	// Check last pipeline status
	if rc.LastPipeline != nil && rc.LastPipeline.Status != "success" {
		severity := "Warning"
		if rc.LastPipeline.Status == "failed" {
			severity = "Error"
		}
		
		issue := models.Issue{
			Source:      "GitLab",
			Category:    "PipelineIssue",
			Severity:    severity,
			Title:       "GitLab Pipeline Issue",
			Description: fmt.Sprintf("Pipeline #%d status: %s", rc.LastPipeline.ID, rc.LastPipeline.Status),
		}
		result.Issues = append(result.Issues, issue)
	}
	
	// Check last deployment status
	if rc.LastDeployment != nil && rc.LastDeployment.Status != "success" {
		severity := "Warning"
		if rc.LastDeployment.Status == "failed" {
			severity = "Error"
		}
		
		issue := models.Issue{
			Source:      "GitLab",
			Category:    "DeploymentIssue",
			Severity:    severity,
			Title:       "GitLab Deployment Issue",
			Description: fmt.Sprintf("Deployment to %s status: %s", rc.LastDeployment.Environment.Name, rc.LastDeployment.Status),
		}
		result.Issues = append(result.Issues, issue)
	}
}

// generateRecommendations creates recommendations based on identified issues
func (tc *TroubleshootCorrelator) generateRecommendations(result *models.TroubleshootResult) {
    // Update the original implementation to include more recommendations
    recommendationMap := make(map[string]bool)
    
    for _, issue := range result.Issues {
        switch issue.Category {
        case "ImagePullError":
            recommendationMap["Check image name and credentials for accessing private registries."] = true
            recommendationMap["Verify that the image tag exists in the registry."] = true
            
        case "HealthCheckFailure":
            recommendationMap["Review liveness and readiness probe configuration."] = true
            recommendationMap["Check application logs for errors during startup."] = true
            
        case "ResourceIssue":
            recommendationMap["Review resource requests and limits in the deployment."] = true
            recommendationMap["Monitor resource usage to determine appropriate values."] = true
            
        case "CrashLoopBackOff":
            recommendationMap["Check container logs for errors."] = true
            recommendationMap["Verify environment variables and configuration."] = true
            
        case "SyncIssue", "SyncFailure":
            recommendationMap["Check ArgoCD application manifest for errors."] = true
            recommendationMap["Verify that the target revision exists in the Git repository."] = true
            
        case "PipelineIssue":
            recommendationMap["Review GitLab pipeline logs for errors."] = true
            recommendationMap["Check if the pipeline configuration is valid."] = true
            
        case "DeploymentIssue":
            recommendationMap["Check GitLab deployment job logs for errors."] = true
            recommendationMap["Verify deployment environment configuration."] = true
            
        case "PodNotRunning", "PodNotReady", "PodInitializing":
            recommendationMap["Check pod events for scheduling or initialization issues."] = true
            recommendationMap["Examine init container logs for errors."] = true
            
        case "InitializationIssue":
            recommendationMap["Check init container logs for errors."] = true
            recommendationMap["Verify that volumes can be mounted properly."] = true
            
        case "ContainerReadinessIssue":
            recommendationMap["Review readiness probe configuration."] = true
            recommendationMap["Check container logs for application startup issues."] = true
            
        case "VolumeIssue":
            recommendationMap["Verify that PersistentVolumeClaims are bound."] = true
            recommendationMap["Check if storage classes are properly configured."] = true
            recommendationMap["Ensure sufficient storage space is available on the nodes."] = true
            
        case "SchedulingIssue":
            recommendationMap["Check if nodes have sufficient resources for the pod."] = true
            recommendationMap["Verify that node selectors or taints are not preventing scheduling."] = true
        }
    }
    
    // Add generic recommendations if no specific issues found
    if len(result.Issues) == 0 {
        recommendationMap["Check pod logs for errors."] = true
        recommendationMap["Examine Kubernetes events for the resource."] = true
        recommendationMap["Verify network connectivity between components."] = true
    }
    
    // Convert map to slice
    for rec := range recommendationMap {
        result.Recommendations = append(result.Recommendations, rec)
    }
}


```
Page 3/5FirstPrevNextLast