diff --git a/README.md b/README.md
index f3b92aac5..1f49174b4 100644
--- a/README.md
+++ b/README.md
@@ -304,13 +304,14 @@ The following sets of tools are available (toolsets marked with ✓ in the Defau
-| Toolset | Description | Default |
-|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|
-| config | View and manage the current local Kubernetes configuration (kubeconfig) | ✓ |
-| core | Most common tools for Kubernetes management (Pods, Generic Resources, Events, etc.) | ✓ |
-| helm | Tools for managing Helm charts and releases | ✓ |
-| kiali | Most common tools for managing Kiali, check the [Kiali documentation](https://github.com/containers/kubernetes-mcp-server/blob/main/docs/KIALI.md) for more details. | |
-| kubevirt | KubeVirt virtual machine management tools | |
+| Toolset | Description | Default |
+|------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|
+| config | View and manage the current local Kubernetes configuration (kubeconfig) | ✓ |
+| core | Most common tools for Kubernetes management (Pods, Generic Resources, Events, etc.) | ✓ |
+| external-secrets | Tools for managing External Secrets Operator for Red Hat OpenShift - operator installation, configuration, SecretStore/ExternalSecret management, and debugging | |
+| helm | Tools for managing Helm charts and releases | ✓ |
+| kiali | Most common tools for managing Kiali, check the [Kiali documentation](https://github.com/containers/kubernetes-mcp-server/blob/main/docs/KIALI.md) for more details. | |
+| kubevirt | KubeVirt virtual machine management tools | |
@@ -430,6 +431,209 @@ In case multi-cluster support is enabled (default) and you have access to multip
+external-secrets
+
+- **external_secrets_operator_install** - Install the External Secrets Operator for Red Hat OpenShift via OLM (Operator Lifecycle Manager).
+This creates the required Namespace, OperatorGroup, and Subscription resources.
+The operator will be installed in the 'external-secrets-operator' namespace.
+Reference: https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html/security_and_compliance/external-secrets-operator-for-red-hat-openshift
+ - `approval` (`string`) - Install plan approval strategy: 'Automatic' or 'Manual' (default: 'Automatic')
+ - `channel` (`string`) - Subscription channel (default: 'stable')
+
+- **external_secrets_operator_status** - Get the status of the External Secrets Operator installation.
+Returns information about the Subscription, ClusterServiceVersion (CSV), and operator deployment status.
+Use this to verify if the operator is installed and running correctly.
+
+- **external_secrets_operator_uninstall** - Uninstall the External Secrets Operator for Red Hat OpenShift.
+This removes the Subscription and ClusterServiceVersion (CSV) resources.
+WARNING: This will remove the operator but NOT the CRDs or existing ExternalSecrets/SecretStores.
+ - `delete_namespace` (`boolean`) - Also delete the operator namespace (default: false)
+
+- **external_secrets_config_get** - Get the ExternalSecretsConfig resource which controls the operator configuration.
+The ExternalSecretsConfig API allows you to customize operator behavior such as:
+- Controller deployment settings
+- Webhook configuration
+- Cert controller settings
+Reference: https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html/security_and_compliance/external-secrets-operator-for-red-hat-openshift#customizing-the-external-secrets-operator-for-red-hat-openshift
+ - `name` (`string`) - Name of the ExternalSecretsConfig resource (default: 'cluster')
+
+- **external_secrets_config_apply** - Create or update the ExternalSecretsConfig resource to configure the operator.
+The ExternalSecretsConfig controls operator deployment settings, webhook configuration, etc.
+Example configuration YAML:
+ apiVersion: operator.external-secrets.io/v1alpha1
+ kind: ExternalSecretsConfig
+ metadata:
+ name: cluster
+ spec:
+ fullnameOverride: my-external-secrets
+Reference: https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html/security_and_compliance/external-secrets-operator-for-red-hat-openshift#customizing-the-external-secrets-operator-for-red-hat-openshift
+ - `config` (`string`) **(required)** - YAML or JSON representation of the ExternalSecretsConfig resource
+
+- **external_secrets_store_list** - List SecretStores and/or ClusterSecretStores in the cluster.
+SecretStore is a namespaced resource that specifies how to access a secret provider (AWS, GCP, Azure, Vault, etc.).
+ClusterSecretStore is a cluster-scoped variant that can be referenced from any namespace.
+Reference: https://external-secrets.io/latest/api/secretstore/
+ - `cluster_scoped` (`boolean`) - If true, list ClusterSecretStores instead of SecretStores (default: false)
+ - `namespace` (`string`) - Namespace to list SecretStores from (optional, lists from all namespaces if not provided)
+
+- **external_secrets_store_get** - Get details of a SecretStore or ClusterSecretStore.
+Returns the full specification and current status including validation state and capabilities.
+Reference: https://external-secrets.io/latest/api/secretstore/
+ - `cluster_scoped` (`boolean`) - If true, get a ClusterSecretStore instead of SecretStore (default: false)
+ - `name` (`string`) **(required)** - Name of the SecretStore or ClusterSecretStore
+ - `namespace` (`string`) - Namespace of the SecretStore (not needed for ClusterSecretStore)
+
+- **external_secrets_store_create** - Create or update a SecretStore or ClusterSecretStore.
+Supports various providers: AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, HashiCorp Vault,
+Kubernetes Secrets, Bitwarden, 1Password, and many more.
+
+Example SecretStore for AWS Secrets Manager:
+ apiVersion: external-secrets.io/v1
+ kind: SecretStore
+ metadata:
+ name: aws-secretsmanager
+ namespace: my-namespace
+ spec:
+ provider:
+ aws:
+ service: SecretsManager
+ region: us-east-1
+ auth:
+ secretRef:
+ accessKeyIDSecretRef:
+ name: aws-credentials
+ key: access-key
+ secretAccessKeySecretRef:
+ name: aws-credentials
+ key: secret-access-key
+
+Reference: https://external-secrets.io/latest/provider/aws-secrets-manager/
+ - `store` (`string`) **(required)** - YAML or JSON representation of the SecretStore or ClusterSecretStore resource
+
+- **external_secrets_store_delete** - Delete a SecretStore or ClusterSecretStore.
+WARNING: Deleting a store will cause ExternalSecrets referencing it to fail syncing.
+ - `cluster_scoped` (`boolean`) - If true, delete a ClusterSecretStore instead of SecretStore (default: false)
+ - `name` (`string`) **(required)** - Name of the SecretStore or ClusterSecretStore to delete
+ - `namespace` (`string`) - Namespace of the SecretStore (not needed for ClusterSecretStore)
+
+- **external_secrets_store_validate** - Check the validation status of SecretStores and/or ClusterSecretStores.
+Returns a summary of store health including:
+- Whether the store is valid and ready
+- Capabilities (ReadOnly, ReadWrite)
+- Any error conditions
+Use this to quickly identify stores with configuration issues.
+ - `include_cluster_stores` (`boolean`) - Also include ClusterSecretStores in the validation check (default: true)
+ - `namespace` (`string`) - Namespace to check SecretStores in (optional, checks all namespaces if not provided)
+
+- **external_secrets_list** - List ExternalSecrets and/or ClusterExternalSecrets in the cluster.
+ExternalSecret is a namespaced resource that defines what secret data to fetch from a SecretStore.
+ClusterExternalSecret can create ExternalSecrets across multiple namespaces.
+Reference: https://external-secrets.io/latest/api/externalsecret/
+ - `cluster_scoped` (`boolean`) - If true, list ClusterExternalSecrets instead of ExternalSecrets (default: false)
+ - `namespace` (`string`) - Namespace to list ExternalSecrets from (optional, lists from all namespaces if not provided)
+
+- **external_secrets_get** - Get details of an ExternalSecret or ClusterExternalSecret.
+Returns the full specification, sync status, and any error conditions.
+Reference: https://external-secrets.io/latest/api/externalsecret/
+ - `cluster_scoped` (`boolean`) - If true, get a ClusterExternalSecret instead of ExternalSecret (default: false)
+ - `name` (`string`) **(required)** - Name of the ExternalSecret or ClusterExternalSecret
+ - `namespace` (`string`) - Namespace of the ExternalSecret (not needed for ClusterExternalSecret)
+
+- **external_secrets_create** - Create or update an ExternalSecret or ClusterExternalSecret.
+ExternalSecret defines how to fetch secret data from a provider and create a Kubernetes Secret.
+
+Example ExternalSecret:
+ apiVersion: external-secrets.io/v1
+ kind: ExternalSecret
+ metadata:
+ name: my-secret
+ namespace: my-namespace
+ spec:
+ refreshInterval: 1h
+ secretStoreRef:
+ name: aws-secretsmanager
+ kind: SecretStore
+ target:
+ name: my-k8s-secret
+ creationPolicy: Owner
+ data:
+ - secretKey: password
+ remoteRef:
+ key: my-aws-secret
+ property: password
+
+Reference: https://external-secrets.io/latest/api/externalsecret/
+ - `secret` (`string`) **(required)** - YAML or JSON representation of the ExternalSecret or ClusterExternalSecret resource
+
+- **external_secrets_delete** - Delete an ExternalSecret or ClusterExternalSecret.
+Note: By default, the associated Kubernetes Secret will also be deleted (depending on creationPolicy).
+ - `cluster_scoped` (`boolean`) - If true, delete a ClusterExternalSecret instead of ExternalSecret (default: false)
+ - `name` (`string`) **(required)** - Name of the ExternalSecret or ClusterExternalSecret to delete
+ - `namespace` (`string`) - Namespace of the ExternalSecret (not needed for ClusterExternalSecret)
+
+- **external_secrets_sync_status** - Check the synchronization status of ExternalSecrets.
+Returns a summary of sync health including:
+- Whether secrets are synced successfully
+- Last sync time and refresh interval
+- Any sync errors or issues
+Use this to quickly identify ExternalSecrets with sync problems.
+ - `name` (`string`) - Specific ExternalSecret name to check (optional, checks all if not provided)
+ - `namespace` (`string`) - Namespace to check ExternalSecrets in (optional, checks all namespaces if not provided)
+
+- **external_secrets_refresh** - Trigger a refresh of an ExternalSecret to immediately sync from the provider.
+This adds an annotation to force the controller to re-sync the secret data.
+Useful when you've updated the secret in the provider and want immediate sync.
+ - `name` (`string`) **(required)** - Name of the ExternalSecret to refresh
+ - `namespace` (`string`) - Namespace of the ExternalSecret
+
+- **external_secrets_debug** - Comprehensive debugging tool for External Secrets Operator issues.
+Collects diagnostic information including:
+- Operator deployment status and logs
+- ExternalSecretsConfig status
+- SecretStore/ClusterSecretStore validation status
+- ExternalSecret sync status and errors
+- Related Kubernetes events
+Use this when troubleshooting sync failures or operator issues.
+Reference: https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html/security_and_compliance/external-secrets-operator-for-red-hat-openshift
+ - `include_logs` (`boolean`) - Include operator pod logs in the debug output (default: true)
+ - `log_tail_lines` (`integer`) - Number of log lines to include (default: 50)
+ - `namespace` (`string`) - Namespace to focus debugging on (optional, collects cluster-wide info if not provided)
+
+- **external_secrets_events** - Get Kubernetes events related to External Secrets resources.
+Filters events for ExternalSecret, SecretStore, ClusterSecretStore, and ClusterExternalSecret resources.
+Useful for troubleshooting sync failures and understanding what's happening.
+ - `namespace` (`string`) - Namespace to get events from (optional, gets from all namespaces if not provided)
+ - `resource_name` (`string`) - Filter events for a specific resource name (optional)
+
+- **external_secrets_logs** - Get logs from the External Secrets Operator pods.
+Retrieves logs from the operator controller, webhook, and cert-controller pods.
+Useful for diagnosing operator-level issues.
+ - `container` (`string`) - Specific container to get logs from (optional, gets all containers if not provided)
+ - `previous` (`boolean`) - Get logs from previous container instance (default: false)
+ - `tail_lines` (`integer`) - Number of lines to retrieve from the end of the logs (default: 100)
+
+- **external_secrets_health** - Quick health check for the External Secrets Operator and resources.
+Returns a summary of:
+- Operator installation status
+- Number of healthy/unhealthy SecretStores
+- Number of synced/failed ExternalSecrets
+- Any critical issues detected
+Use this for a quick overview of the External Secrets health.
+ - `namespace` (`string`) - Namespace to check health for (optional, checks cluster-wide if not provided)
+
+- **external_secrets_guide** - Get guidance and examples for using External Secrets Operator.
+Provides documentation, examples, and best practices for:
+- Setting up different secret providers (AWS, GCP, Azure, Vault, etc.)
+- Creating SecretStores and ExternalSecrets
+- Troubleshooting common issues
+- Security best practices
+ - `provider` (`string`) - Specific provider to get examples for: 'aws', 'gcp', 'azure', 'vault', 'kubernetes' (only used when topic is 'providers')
+ - `topic` (`string`) - Topic to get guidance on: 'providers', 'secretstore', 'externalsecret', 'troubleshooting', 'security', or 'overview' (default: 'overview')
+
+
+
+
+
helm
- **helm_install** - Install a Helm chart in the current or provided namespace
diff --git a/docs/EXTERNAL_SECRETS.md b/docs/EXTERNAL_SECRETS.md
new file mode 100644
index 000000000..948b6423a
--- /dev/null
+++ b/docs/EXTERNAL_SECRETS.md
@@ -0,0 +1,232 @@
+# External Secrets Operator for Red Hat OpenShift
+
+This document provides guidance on using the External Secrets toolset with the Kubernetes MCP Server.
+
+## Overview
+
+The External Secrets Operator for Red Hat OpenShift synchronizes secrets from external secret management systems (AWS Secrets Manager, HashiCorp Vault, Google Cloud Secret Manager, Azure Key Vault, etc.) into Kubernetes Secrets.
+
+The `external-secrets` toolset provides comprehensive tools for:
+- **Operator lifecycle management** - Install, configure, and uninstall the operator
+- **SecretStore management** - Create and manage connections to secret providers
+- **ExternalSecret management** - Define and manage secret synchronization
+- **Debugging and monitoring** - Health checks, logs, events, and diagnostics
+
+## Prerequisites
+
+- OpenShift Container Platform 4.x cluster
+- Cluster administrator access (for operator installation)
+- Access to an external secrets provider (AWS, GCP, Azure, Vault, etc.)
+
+## Enabling the Toolset
+
+The `external-secrets` toolset is not enabled by default. Enable it using the `--toolsets` flag:
+
+```bash
+kubernetes-mcp-server --toolsets core,config,helm,external-secrets
+```
+
+Or in your MCP client configuration:
+
+```json
+{
+ "mcpServers": {
+ "kubernetes": {
+ "command": "npx",
+ "args": [
+ "-y",
+ "kubernetes-mcp-server@latest",
+ "--toolsets", "core,config,helm,external-secrets"
+ ]
+ }
+ }
+}
+```
+
+## Available Tools
+
+### Operator Management
+
+| Tool | Description |
+|------|-------------|
+| `external_secrets_operator_install` | Install the External Secrets Operator via OLM |
+| `external_secrets_operator_status` | Get operator installation status |
+| `external_secrets_operator_uninstall` | Uninstall the operator |
+| `external_secrets_config_get` | Get ExternalSecretsConfig resource |
+| `external_secrets_config_apply` | Apply/update ExternalSecretsConfig |
+
+### SecretStore Management
+
+| Tool | Description |
+|------|-------------|
+| `external_secrets_store_list` | List SecretStores/ClusterSecretStores |
+| `external_secrets_store_get` | Get details of a specific store |
+| `external_secrets_store_create` | Create/update a SecretStore |
+| `external_secrets_store_delete` | Delete a SecretStore |
+| `external_secrets_store_validate` | Validate store health status |
+
+### ExternalSecret Management
+
+| Tool | Description |
+|------|-------------|
+| `external_secrets_list` | List ExternalSecrets/ClusterExternalSecrets |
+| `external_secrets_get` | Get details of a specific ExternalSecret |
+| `external_secrets_create` | Create/update an ExternalSecret |
+| `external_secrets_delete` | Delete an ExternalSecret |
+| `external_secrets_sync_status` | Check synchronization status |
+| `external_secrets_refresh` | Trigger immediate secret refresh |
+
+### Debugging and Monitoring
+
+| Tool | Description |
+|------|-------------|
+| `external_secrets_debug` | Comprehensive debugging information |
+| `external_secrets_events` | View related Kubernetes events |
+| `external_secrets_logs` | Get operator pod logs |
+| `external_secrets_health` | Quick health check summary |
+| `external_secrets_guide` | Get documentation and examples |
+
+## Quick Start
+
+### 1. Install the Operator
+
+```
+Use tool: external_secrets_operator_install
+```
+
+This creates:
+- `external-secrets-operator` namespace
+- OperatorGroup
+- Subscription to the Red Hat operator catalog
+
+### 2. Verify Installation
+
+```
+Use tool: external_secrets_operator_status
+```
+
+Wait until the operator pods are running.
+
+### 3. Create a SecretStore
+
+Example for AWS Secrets Manager:
+
+```
+Use tool: external_secrets_store_create
+Argument store:
+apiVersion: external-secrets.io/v1
+kind: SecretStore
+metadata:
+ name: aws-secretsmanager
+ namespace: my-namespace
+spec:
+ provider:
+ aws:
+ service: SecretsManager
+ region: us-east-1
+ auth:
+ secretRef:
+ accessKeyIDSecretRef:
+ name: aws-credentials
+ key: access-key
+ secretAccessKeySecretRef:
+ name: aws-credentials
+ key: secret-access-key
+```
+
+### 4. Create an ExternalSecret
+
+```
+Use tool: external_secrets_create
+Argument secret:
+apiVersion: external-secrets.io/v1
+kind: ExternalSecret
+metadata:
+ name: my-secret
+ namespace: my-namespace
+spec:
+ refreshInterval: 1h
+ secretStoreRef:
+ name: aws-secretsmanager
+ kind: SecretStore
+ target:
+ name: my-k8s-secret
+ data:
+ - secretKey: password
+ remoteRef:
+ key: my-aws-secret
+ property: password
+```
+
+### 5. Verify Sync Status
+
+```
+Use tool: external_secrets_sync_status
+Argument namespace: my-namespace
+```
+
+## Supported Providers
+
+The External Secrets Operator supports many providers:
+
+- **Cloud Providers**: AWS Secrets Manager, AWS Parameter Store, GCP Secret Manager, Azure Key Vault
+- **Secret Management**: HashiCorp Vault, CyberArk Conjur, Bitwarden, 1Password, Doppler
+- **Other**: Kubernetes Secrets, Webhook (custom HTTP endpoints)
+
+Use the guide tool for provider-specific examples:
+
+```
+Use tool: external_secrets_guide
+Argument topic: providers
+Argument provider: aws
+```
+
+## Troubleshooting
+
+### Quick Health Check
+
+```
+Use tool: external_secrets_health
+```
+
+### Comprehensive Debug
+
+```
+Use tool: external_secrets_debug
+Argument include_logs: true
+```
+
+### Common Issues
+
+1. **SecretStore not valid**: Check credentials and network connectivity
+2. **ExternalSecret not syncing**: Verify SecretStore is ready and secret exists in provider
+3. **Operator not running**: Check subscription approval mode and resource availability
+
+For detailed troubleshooting guidance:
+
+```
+Use tool: external_secrets_guide
+Argument topic: troubleshooting
+```
+
+## Security Best Practices
+
+- Use separate SecretStores per namespace/team for isolation
+- Prefer workload identity (IRSA, Workload Identity) over static credentials
+- Set appropriate RBAC to restrict SecretStore creation
+- Enable audit logging for external-secrets resources
+
+For more security guidance:
+
+```
+Use tool: external_secrets_guide
+Argument topic: security
+```
+
+## References
+
+- [External Secrets Operator for Red Hat OpenShift Documentation](https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html/security_and_compliance/external-secrets-operator-for-red-hat-openshift)
+- [External Secrets Documentation](https://external-secrets.io/latest/)
+- [OpenShift External Secrets Operator Repository](https://github.com/openshift/external-secrets-operator)
+- [External Secrets Repository](https://github.com/openshift/external-secrets)
+
diff --git a/internal/tools/update-readme/main.go b/internal/tools/update-readme/main.go
index d400afe4b..f1435368a 100644
--- a/internal/tools/update-readme/main.go
+++ b/internal/tools/update-readme/main.go
@@ -15,6 +15,7 @@ import (
_ "github.com/containers/kubernetes-mcp-server/pkg/toolsets/config"
_ "github.com/containers/kubernetes-mcp-server/pkg/toolsets/core"
+ _ "github.com/containers/kubernetes-mcp-server/pkg/toolsets/externalsecrets"
_ "github.com/containers/kubernetes-mcp-server/pkg/toolsets/helm"
_ "github.com/containers/kubernetes-mcp-server/pkg/toolsets/kiali"
_ "github.com/containers/kubernetes-mcp-server/pkg/toolsets/kubevirt"
diff --git a/pkg/mcp/modules.go b/pkg/mcp/modules.go
index 255f42177..e7046439f 100644
--- a/pkg/mcp/modules.go
+++ b/pkg/mcp/modules.go
@@ -3,6 +3,7 @@ package mcp
import (
_ "github.com/containers/kubernetes-mcp-server/pkg/toolsets/config"
_ "github.com/containers/kubernetes-mcp-server/pkg/toolsets/core"
+ _ "github.com/containers/kubernetes-mcp-server/pkg/toolsets/externalsecrets"
_ "github.com/containers/kubernetes-mcp-server/pkg/toolsets/helm"
_ "github.com/containers/kubernetes-mcp-server/pkg/toolsets/kiali"
_ "github.com/containers/kubernetes-mcp-server/pkg/toolsets/kubevirt"
diff --git a/pkg/toolsets/externalsecrets/helpers.go b/pkg/toolsets/externalsecrets/helpers.go
new file mode 100644
index 000000000..00720d10c
--- /dev/null
+++ b/pkg/toolsets/externalsecrets/helpers.go
@@ -0,0 +1,39 @@
+package externalsecrets
+
+import (
+ "time"
+
+ "github.com/containers/kubernetes-mcp-server/pkg/api"
+)
+
+// getStringArg retrieves a string argument from the tool parameters with a default value
+func getStringArg(params api.ToolHandlerParams, key, defaultVal string) string {
+ if val, ok := params.GetArguments()[key].(string); ok && val != "" {
+ return val
+ }
+ return defaultVal
+}
+
+// getBoolArg retrieves a boolean argument from the tool parameters with a default value
+func getBoolArg(params api.ToolHandlerParams, key string, defaultVal bool) bool {
+ if val, ok := params.GetArguments()[key].(bool); ok {
+ return val
+ }
+ return defaultVal
+}
+
+// getIntArg retrieves an integer argument from the tool parameters with a default value
+func getIntArg(params api.ToolHandlerParams, key string, defaultVal int) int {
+ if val, ok := params.GetArguments()[key].(float64); ok {
+ return int(val)
+ }
+ if val, ok := params.GetArguments()[key].(int); ok {
+ return val
+ }
+ return defaultVal
+}
+
+// getCurrentTimestamp returns the current Unix timestamp
+func getCurrentTimestamp() int64 {
+ return time.Now().Unix()
+}
diff --git a/pkg/toolsets/externalsecrets/operator.go b/pkg/toolsets/externalsecrets/operator.go
new file mode 100644
index 000000000..604a8a290
--- /dev/null
+++ b/pkg/toolsets/externalsecrets/operator.go
@@ -0,0 +1,408 @@
+package externalsecrets
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/google/jsonschema-go/jsonschema"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ "k8s.io/utils/ptr"
+
+ "github.com/containers/kubernetes-mcp-server/pkg/api"
+ "github.com/containers/kubernetes-mcp-server/pkg/kubernetes"
+ "github.com/containers/kubernetes-mcp-server/pkg/output"
+)
+
+const (
+ // Operator constants
+ externalSecretsOperatorNamespace = "external-secrets-operator"
+ externalSecretsOperatorName = "external-secrets-operator"
+ externalSecretsOperatorChannel = "stable"
+ externalSecretsOperatorCatalogSource = "redhat-operators"
+ externalSecretsOperatorSourceNS = "openshift-marketplace"
+
+ // API Groups
+ operatorsAPIGroup = "operators.coreos.com"
+ externalSecretsOperatorAPIGrp = "operator.external-secrets.io"
+)
+
+func initOperatorTools() []api.ServerTool {
+ return []api.ServerTool{
+ {
+ Tool: api.Tool{
+ Name: "external_secrets_operator_install",
+ Description: `Install the External Secrets Operator for Red Hat OpenShift via OLM (Operator Lifecycle Manager).
+This creates the required Namespace, OperatorGroup, and Subscription resources.
+The operator will be installed in the 'external-secrets-operator' namespace.
+Reference: https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html/security_and_compliance/external-secrets-operator-for-red-hat-openshift`,
+ InputSchema: &jsonschema.Schema{
+ Type: "object",
+ Properties: map[string]*jsonschema.Schema{
+ "channel": {
+ Type: "string",
+ Description: "Subscription channel (default: 'stable')",
+ Default: api.ToRawMessage("stable"),
+ },
+ "approval": {
+ Type: "string",
+ Description: "Install plan approval strategy: 'Automatic' or 'Manual' (default: 'Automatic')",
+ Default: api.ToRawMessage("Automatic"),
+ Enum: []interface{}{"Automatic", "Manual"},
+ },
+ },
+ },
+ Annotations: api.ToolAnnotations{
+ Title: "External Secrets: Install Operator",
+ ReadOnlyHint: ptr.To(false),
+ DestructiveHint: ptr.To(false),
+ IdempotentHint: ptr.To(true),
+ OpenWorldHint: ptr.To(true),
+ },
+ },
+ Handler: operatorInstall,
+ },
+ {
+ Tool: api.Tool{
+ Name: "external_secrets_operator_status",
+ Description: `Get the status of the External Secrets Operator installation.
+Returns information about the Subscription, ClusterServiceVersion (CSV), and operator deployment status.
+Use this to verify if the operator is installed and running correctly.`,
+ InputSchema: &jsonschema.Schema{
+ Type: "object",
+ Properties: map[string]*jsonschema.Schema{},
+ },
+ Annotations: api.ToolAnnotations{
+ Title: "External Secrets: Operator Status",
+ ReadOnlyHint: ptr.To(true),
+ DestructiveHint: ptr.To(false),
+ OpenWorldHint: ptr.To(true),
+ },
+ },
+ Handler: operatorStatus,
+ },
+ {
+ Tool: api.Tool{
+ Name: "external_secrets_operator_uninstall",
+ Description: `Uninstall the External Secrets Operator for Red Hat OpenShift.
+This removes the Subscription and ClusterServiceVersion (CSV) resources.
+WARNING: This will remove the operator but NOT the CRDs or existing ExternalSecrets/SecretStores.`,
+ InputSchema: &jsonschema.Schema{
+ Type: "object",
+ Properties: map[string]*jsonschema.Schema{
+ "delete_namespace": {
+ Type: "boolean",
+ Description: "Also delete the operator namespace (default: false)",
+ Default: api.ToRawMessage(false),
+ },
+ },
+ },
+ Annotations: api.ToolAnnotations{
+ Title: "External Secrets: Uninstall Operator",
+ ReadOnlyHint: ptr.To(false),
+ DestructiveHint: ptr.To(true),
+ IdempotentHint: ptr.To(true),
+ OpenWorldHint: ptr.To(true),
+ },
+ },
+ Handler: operatorUninstall,
+ },
+ {
+ Tool: api.Tool{
+ Name: "external_secrets_config_get",
+ Description: `Get the ExternalSecretsConfig resource which controls the operator configuration.
+The ExternalSecretsConfig API allows you to customize operator behavior such as:
+- Controller deployment settings
+- Webhook configuration
+- Cert controller settings
+Reference: https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html/security_and_compliance/external-secrets-operator-for-red-hat-openshift#customizing-the-external-secrets-operator-for-red-hat-openshift`,
+ InputSchema: &jsonschema.Schema{
+ Type: "object",
+ Properties: map[string]*jsonschema.Schema{
+ "name": {
+ Type: "string",
+ Description: "Name of the ExternalSecretsConfig resource (default: 'cluster')",
+ Default: api.ToRawMessage("cluster"),
+ },
+ },
+ },
+ Annotations: api.ToolAnnotations{
+ Title: "External Secrets: Get Operator Config",
+ ReadOnlyHint: ptr.To(true),
+ DestructiveHint: ptr.To(false),
+ OpenWorldHint: ptr.To(true),
+ },
+ },
+ Handler: configGet,
+ },
+ {
+ Tool: api.Tool{
+ Name: "external_secrets_config_apply",
+ Description: `Create or update the ExternalSecretsConfig resource to configure the operator.
+The ExternalSecretsConfig controls operator deployment settings, webhook configuration, etc.
+Example configuration YAML:
+ apiVersion: operator.external-secrets.io/v1alpha1
+ kind: ExternalSecretsConfig
+ metadata:
+ name: cluster
+ spec:
+ fullnameOverride: my-external-secrets
+Reference: https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html/security_and_compliance/external-secrets-operator-for-red-hat-openshift#customizing-the-external-secrets-operator-for-red-hat-openshift`,
+ InputSchema: &jsonschema.Schema{
+ Type: "object",
+ Properties: map[string]*jsonschema.Schema{
+ "config": {
+ Type: "string",
+ Description: "YAML or JSON representation of the ExternalSecretsConfig resource",
+ },
+ },
+ Required: []string{"config"},
+ },
+ Annotations: api.ToolAnnotations{
+ Title: "External Secrets: Apply Operator Config",
+ ReadOnlyHint: ptr.To(false),
+ DestructiveHint: ptr.To(false),
+ IdempotentHint: ptr.To(true),
+ OpenWorldHint: ptr.To(true),
+ },
+ },
+ Handler: configApply,
+ },
+ }
+}
+
+func operatorInstall(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
+ channel := getStringArg(params, "channel", externalSecretsOperatorChannel)
+ approval := getStringArg(params, "approval", "Automatic")
+
+ // Create resources in order: Namespace, OperatorGroup, Subscription
+ resources := fmt.Sprintf(`---
+apiVersion: v1
+kind: Namespace
+metadata:
+ name: %s
+ labels:
+ openshift.io/cluster-monitoring: "true"
+---
+apiVersion: operators.coreos.com/v1
+kind: OperatorGroup
+metadata:
+ name: %s
+ namespace: %s
+spec: {}
+---
+apiVersion: operators.coreos.com/v1alpha1
+kind: Subscription
+metadata:
+ name: %s
+ namespace: %s
+spec:
+ channel: %s
+ installPlanApproval: %s
+ name: %s
+ source: %s
+ sourceNamespace: %s
+`,
+ externalSecretsOperatorNamespace,
+ externalSecretsOperatorName,
+ externalSecretsOperatorNamespace,
+ externalSecretsOperatorName,
+ externalSecretsOperatorNamespace,
+ channel,
+ approval,
+ externalSecretsOperatorName,
+ externalSecretsOperatorCatalogSource,
+ externalSecretsOperatorSourceNS,
+ )
+
+ result, err := params.ResourcesCreateOrUpdate(params, resources)
+ if err != nil {
+ return api.NewToolCallResult("", fmt.Errorf("failed to install External Secrets Operator: %w", err)), nil
+ }
+
+ marshalledYaml, err := output.MarshalYaml(result)
+ if err != nil {
+ return api.NewToolCallResult("", fmt.Errorf("failed to marshal result: %w", err)), nil
+ }
+
+ return api.NewToolCallResult(
+ "# External Secrets Operator installation initiated\n"+
+ "The operator is being installed. Use 'external_secrets_operator_status' to monitor the installation progress.\n\n"+
+ "# Created resources:\n"+marshalledYaml,
+ nil,
+ ), nil
+}
+
+func operatorStatus(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
+ var statusParts []string
+ statusParts = append(statusParts, "# External Secrets Operator Status\n")
+
+ // Check Subscription
+ subscriptionGVK := &schema.GroupVersionKind{
+ Group: operatorsAPIGroup,
+ Version: "v1alpha1",
+ Kind: "Subscription",
+ }
+ sub, err := params.ResourcesGet(params, subscriptionGVK, externalSecretsOperatorNamespace, externalSecretsOperatorName)
+ if err != nil {
+ statusParts = append(statusParts, fmt.Sprintf("## Subscription\nNot found or error: %v\n", err))
+ } else {
+ subYaml, _ := output.MarshalYaml(sub)
+ statusParts = append(statusParts, fmt.Sprintf("## Subscription\n```yaml\n%s```\n", subYaml))
+ }
+
+ // Check CSV (ClusterServiceVersion) - list all in namespace
+ csvGVK := &schema.GroupVersionKind{
+ Group: operatorsAPIGroup,
+ Version: "v1alpha1",
+ Kind: "ClusterServiceVersion",
+ }
+ csvList, err := params.ResourcesList(params, csvGVK, externalSecretsOperatorNamespace, kubernetes.ResourceListOptions{AsTable: false})
+ if err != nil {
+ statusParts = append(statusParts, fmt.Sprintf("## ClusterServiceVersion\nNot found or error: %v\n", err))
+ } else {
+ csvYaml, _ := output.MarshalYaml(csvList)
+ statusParts = append(statusParts, fmt.Sprintf("## ClusterServiceVersion(s)\n```yaml\n%s```\n", csvYaml))
+ }
+
+ // Check operator deployment
+ deploymentGVK := &schema.GroupVersionKind{
+ Group: "apps",
+ Version: "v1",
+ Kind: "Deployment",
+ }
+ deployList, err := params.ResourcesList(params, deploymentGVK, externalSecretsOperatorNamespace, kubernetes.ResourceListOptions{AsTable: true})
+ if err != nil {
+ statusParts = append(statusParts, fmt.Sprintf("## Deployments\nNot found or error: %v\n", err))
+ } else {
+ deployYaml, _ := params.ListOutput.PrintObj(deployList)
+ statusParts = append(statusParts, fmt.Sprintf("## Deployments\n%s\n", deployYaml))
+ }
+
+ // Check ExternalSecretsConfig
+ configGVK := &schema.GroupVersionKind{
+ Group: externalSecretsOperatorAPIGrp,
+ Version: "v1alpha1",
+ Kind: "ExternalSecretsConfig",
+ }
+ config, err := params.ResourcesGet(params, configGVK, "", "cluster")
+ if err != nil {
+ statusParts = append(statusParts, "## ExternalSecretsConfig\nNot found (operator may not be fully installed yet)\n")
+ } else {
+ configYaml, _ := output.MarshalYaml(config)
+ statusParts = append(statusParts, fmt.Sprintf("## ExternalSecretsConfig\n```yaml\n%s```\n", configYaml))
+ }
+
+ return api.NewToolCallResult(strings.Join(statusParts, "\n"), nil), nil
+}
+
+func operatorUninstall(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
+ deleteNamespace := getBoolArg(params, "delete_namespace", false)
+
+ var results []string
+ results = append(results, "# External Secrets Operator Uninstallation\n")
+
+ // Delete Subscription
+ subscriptionGVK := &schema.GroupVersionKind{
+ Group: operatorsAPIGroup,
+ Version: "v1alpha1",
+ Kind: "Subscription",
+ }
+ err := params.ResourcesDelete(params, subscriptionGVK, externalSecretsOperatorNamespace, externalSecretsOperatorName)
+ if err != nil {
+ results = append(results, fmt.Sprintf("- Subscription deletion: %v", err))
+ } else {
+ results = append(results, "- Subscription deleted successfully")
+ }
+
+ // Find and delete CSV
+ csvGVK := &schema.GroupVersionKind{
+ Group: operatorsAPIGroup,
+ Version: "v1alpha1",
+ Kind: "ClusterServiceVersion",
+ }
+ csvList, err := params.ResourcesList(params, csvGVK, externalSecretsOperatorNamespace, kubernetes.ResourceListOptions{AsTable: false})
+ if err == nil {
+ csvListMap := csvList.UnstructuredContent()
+ items, ok := csvListMap["items"].([]interface{})
+ if ok {
+ for _, item := range items {
+ if itemMap, ok := item.(map[string]interface{}); ok {
+ if metadata, ok := itemMap["metadata"].(map[string]interface{}); ok {
+ if name, ok := metadata["name"].(string); ok {
+ if strings.Contains(name, "external-secrets") {
+ err = params.ResourcesDelete(params, csvGVK, externalSecretsOperatorNamespace, name)
+ if err != nil {
+ results = append(results, fmt.Sprintf("- CSV %s deletion: %v", name, err))
+ } else {
+ results = append(results, fmt.Sprintf("- CSV %s deleted successfully", name))
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Optionally delete namespace
+ if deleteNamespace {
+ nsGVK := &schema.GroupVersionKind{
+ Group: "",
+ Version: "v1",
+ Kind: "Namespace",
+ }
+ err = params.ResourcesDelete(params, nsGVK, "", externalSecretsOperatorNamespace)
+ if err != nil {
+ results = append(results, fmt.Sprintf("- Namespace deletion: %v", err))
+ } else {
+ results = append(results, "- Namespace deleted successfully")
+ }
+ }
+
+ results = append(results, "\nNote: CRDs and existing ExternalSecrets/SecretStores are not deleted.")
+
+ return api.NewToolCallResult(strings.Join(results, "\n"), nil), nil
+}
+
+func configGet(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
+ name := getStringArg(params, "name", "cluster")
+
+ configGVK := &schema.GroupVersionKind{
+ Group: externalSecretsOperatorAPIGrp,
+ Version: "v1alpha1",
+ Kind: "ExternalSecretsConfig",
+ }
+
+ config, err := params.ResourcesGet(params, configGVK, "", name)
+ if err != nil {
+ return api.NewToolCallResult("", fmt.Errorf("failed to get ExternalSecretsConfig '%s': %w", name, err)), nil
+ }
+
+ configYaml, _ := output.MarshalYaml(config)
+ return api.NewToolCallResult(
+ "# ExternalSecretsConfig\n```yaml\n"+configYaml+"```",
+ nil,
+ ), nil
+}
+
+func configApply(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
+ config, ok := params.GetArguments()["config"].(string)
+ if !ok || config == "" {
+ return api.NewToolCallResult("", fmt.Errorf("config argument is required")), nil
+ }
+
+ result, err := params.ResourcesCreateOrUpdate(params, config)
+ if err != nil {
+ return api.NewToolCallResult("", fmt.Errorf("failed to apply ExternalSecretsConfig: %w", err)), nil
+ }
+
+ marshalledYaml, err := output.MarshalYaml(result)
+ if err != nil {
+ return api.NewToolCallResult("", fmt.Errorf("failed to marshal result: %w", err)), nil
+ }
+
+ return api.NewToolCallResult(
+ "# ExternalSecretsConfig applied successfully\n```yaml\n"+marshalledYaml+"```",
+ nil,
+ ), nil
+}
diff --git a/pkg/toolsets/externalsecrets/secrets.go b/pkg/toolsets/externalsecrets/secrets.go
new file mode 100644
index 000000000..5be561e51
--- /dev/null
+++ b/pkg/toolsets/externalsecrets/secrets.go
@@ -0,0 +1,601 @@
+package externalsecrets
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/google/jsonschema-go/jsonschema"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ "k8s.io/utils/ptr"
+
+ "github.com/containers/kubernetes-mcp-server/pkg/api"
+ "github.com/containers/kubernetes-mcp-server/pkg/kubernetes"
+ "github.com/containers/kubernetes-mcp-server/pkg/output"
+)
+
+func initSecretTools() []api.ServerTool {
+ return []api.ServerTool{
+ {
+ Tool: api.Tool{
+ Name: "external_secrets_list",
+ Description: `List ExternalSecrets and/or ClusterExternalSecrets in the cluster.
+ExternalSecret is a namespaced resource that defines what secret data to fetch from a SecretStore.
+ClusterExternalSecret can create ExternalSecrets across multiple namespaces.
+Reference: https://external-secrets.io/latest/api/externalsecret/`,
+ InputSchema: &jsonschema.Schema{
+ Type: "object",
+ Properties: map[string]*jsonschema.Schema{
+ "namespace": {
+ Type: "string",
+ Description: "Namespace to list ExternalSecrets from (optional, lists from all namespaces if not provided)",
+ },
+ "cluster_scoped": {
+ Type: "boolean",
+ Description: "If true, list ClusterExternalSecrets instead of ExternalSecrets (default: false)",
+ Default: api.ToRawMessage(false),
+ },
+ },
+ },
+ Annotations: api.ToolAnnotations{
+ Title: "External Secrets: List Secrets",
+ ReadOnlyHint: ptr.To(true),
+ DestructiveHint: ptr.To(false),
+ OpenWorldHint: ptr.To(true),
+ },
+ },
+ Handler: secretList,
+ },
+ {
+ Tool: api.Tool{
+ Name: "external_secrets_get",
+ Description: `Get details of an ExternalSecret or ClusterExternalSecret.
+Returns the full specification, sync status, and any error conditions.
+Reference: https://external-secrets.io/latest/api/externalsecret/`,
+ InputSchema: &jsonschema.Schema{
+ Type: "object",
+ Properties: map[string]*jsonschema.Schema{
+ "name": {
+ Type: "string",
+ Description: "Name of the ExternalSecret or ClusterExternalSecret",
+ },
+ "namespace": {
+ Type: "string",
+ Description: "Namespace of the ExternalSecret (not needed for ClusterExternalSecret)",
+ },
+ "cluster_scoped": {
+ Type: "boolean",
+ Description: "If true, get a ClusterExternalSecret instead of ExternalSecret (default: false)",
+ Default: api.ToRawMessage(false),
+ },
+ },
+ Required: []string{"name"},
+ },
+ Annotations: api.ToolAnnotations{
+ Title: "External Secrets: Get Secret",
+ ReadOnlyHint: ptr.To(true),
+ DestructiveHint: ptr.To(false),
+ OpenWorldHint: ptr.To(true),
+ },
+ },
+ Handler: secretGet,
+ },
+ {
+ Tool: api.Tool{
+ Name: "external_secrets_create",
+ Description: `Create or update an ExternalSecret or ClusterExternalSecret.
+ExternalSecret defines how to fetch secret data from a provider and create a Kubernetes Secret.
+
+Example ExternalSecret:
+ apiVersion: external-secrets.io/v1
+ kind: ExternalSecret
+ metadata:
+ name: my-secret
+ namespace: my-namespace
+ spec:
+ refreshInterval: 1h
+ secretStoreRef:
+ name: aws-secretsmanager
+ kind: SecretStore
+ target:
+ name: my-k8s-secret
+ creationPolicy: Owner
+ data:
+ - secretKey: password
+ remoteRef:
+ key: my-aws-secret
+ property: password
+
+Reference: https://external-secrets.io/latest/api/externalsecret/`,
+ InputSchema: &jsonschema.Schema{
+ Type: "object",
+ Properties: map[string]*jsonschema.Schema{
+ "secret": {
+ Type: "string",
+ Description: "YAML or JSON representation of the ExternalSecret or ClusterExternalSecret resource",
+ },
+ },
+ Required: []string{"secret"},
+ },
+ Annotations: api.ToolAnnotations{
+ Title: "External Secrets: Create/Update Secret",
+ ReadOnlyHint: ptr.To(false),
+ DestructiveHint: ptr.To(false),
+ IdempotentHint: ptr.To(true),
+ OpenWorldHint: ptr.To(true),
+ },
+ },
+ Handler: secretCreate,
+ },
+ {
+ Tool: api.Tool{
+ Name: "external_secrets_delete",
+ Description: `Delete an ExternalSecret or ClusterExternalSecret.
+Note: By default, the associated Kubernetes Secret will also be deleted (depending on creationPolicy).`,
+ InputSchema: &jsonschema.Schema{
+ Type: "object",
+ Properties: map[string]*jsonschema.Schema{
+ "name": {
+ Type: "string",
+ Description: "Name of the ExternalSecret or ClusterExternalSecret to delete",
+ },
+ "namespace": {
+ Type: "string",
+ Description: "Namespace of the ExternalSecret (not needed for ClusterExternalSecret)",
+ },
+ "cluster_scoped": {
+ Type: "boolean",
+ Description: "If true, delete a ClusterExternalSecret instead of ExternalSecret (default: false)",
+ Default: api.ToRawMessage(false),
+ },
+ },
+ Required: []string{"name"},
+ },
+ Annotations: api.ToolAnnotations{
+ Title: "External Secrets: Delete Secret",
+ ReadOnlyHint: ptr.To(false),
+ DestructiveHint: ptr.To(true),
+ IdempotentHint: ptr.To(true),
+ OpenWorldHint: ptr.To(true),
+ },
+ },
+ Handler: secretDelete,
+ },
+ {
+ Tool: api.Tool{
+ Name: "external_secrets_sync_status",
+ Description: `Check the synchronization status of ExternalSecrets.
+Returns a summary of sync health including:
+- Whether secrets are synced successfully
+- Last sync time and refresh interval
+- Any sync errors or issues
+Use this to quickly identify ExternalSecrets with sync problems.`,
+ InputSchema: &jsonschema.Schema{
+ Type: "object",
+ Properties: map[string]*jsonschema.Schema{
+ "namespace": {
+ Type: "string",
+ Description: "Namespace to check ExternalSecrets in (optional, checks all namespaces if not provided)",
+ },
+ "name": {
+ Type: "string",
+ Description: "Specific ExternalSecret name to check (optional, checks all if not provided)",
+ },
+ },
+ },
+ Annotations: api.ToolAnnotations{
+ Title: "External Secrets: Sync Status",
+ ReadOnlyHint: ptr.To(true),
+ DestructiveHint: ptr.To(false),
+ OpenWorldHint: ptr.To(true),
+ },
+ },
+ Handler: secretSyncStatus,
+ },
+ {
+ Tool: api.Tool{
+ Name: "external_secrets_refresh",
+ Description: `Trigger a refresh of an ExternalSecret to immediately sync from the provider.
+This adds an annotation to force the controller to re-sync the secret data.
+Useful when you've updated the secret in the provider and want immediate sync.`,
+ InputSchema: &jsonschema.Schema{
+ Type: "object",
+ Properties: map[string]*jsonschema.Schema{
+ "name": {
+ Type: "string",
+ Description: "Name of the ExternalSecret to refresh",
+ },
+ "namespace": {
+ Type: "string",
+ Description: "Namespace of the ExternalSecret",
+ },
+ },
+ Required: []string{"name"},
+ },
+ Annotations: api.ToolAnnotations{
+ Title: "External Secrets: Refresh",
+ ReadOnlyHint: ptr.To(false),
+ DestructiveHint: ptr.To(false),
+ IdempotentHint: ptr.To(true),
+ OpenWorldHint: ptr.To(true),
+ },
+ },
+ Handler: secretRefresh,
+ },
+ }
+}
+
+func secretList(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
+ namespace := getStringArg(params, "namespace", "")
+ clusterScoped := getBoolArg(params, "cluster_scoped", false)
+
+ kind := "ExternalSecret"
+ if clusterScoped {
+ kind = "ClusterExternalSecret"
+ namespace = ""
+ }
+
+ gvk := &schema.GroupVersionKind{
+ Group: externalSecretsAPIGroup,
+ Version: "v1",
+ Kind: kind,
+ }
+
+ list, err := params.ResourcesList(params, gvk, namespace, kubernetes.ResourceListOptions{AsTable: true})
+ if err != nil {
+ return api.NewToolCallResult("", fmt.Errorf("failed to list %s: %w", kind, err)), nil
+ }
+
+ result, _ := params.ListOutput.PrintObj(list)
+ title := fmt.Sprintf("# %s List", kind)
+ if namespace != "" {
+ title += fmt.Sprintf(" (namespace: %s)", namespace)
+ }
+
+ return api.NewToolCallResult(title+"\n"+result, nil), nil
+}
+
+func secretGet(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
+ name := getStringArg(params, "name", "")
+ if name == "" {
+ return api.NewToolCallResult("", fmt.Errorf("name argument is required")), nil
+ }
+
+ namespace := getStringArg(params, "namespace", "")
+ clusterScoped := getBoolArg(params, "cluster_scoped", false)
+
+ kind := "ExternalSecret"
+ if clusterScoped {
+ kind = "ClusterExternalSecret"
+ namespace = ""
+ }
+
+ gvk := &schema.GroupVersionKind{
+ Group: externalSecretsAPIGroup,
+ Version: "v1",
+ Kind: kind,
+ }
+
+ secret, err := params.ResourcesGet(params, gvk, namespace, name)
+ if err != nil {
+ return api.NewToolCallResult("", fmt.Errorf("failed to get %s '%s': %w", kind, name, err)), nil
+ }
+
+ secretYaml, _ := output.MarshalYaml(secret)
+ return api.NewToolCallResult(
+ fmt.Sprintf("# %s: %s\n```yaml\n%s```", kind, name, secretYaml),
+ nil,
+ ), nil
+}
+
+func secretCreate(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
+ secret, ok := params.GetArguments()["secret"].(string)
+ if !ok || secret == "" {
+ return api.NewToolCallResult("", fmt.Errorf("secret argument is required")), nil
+ }
+
+ result, err := params.ResourcesCreateOrUpdate(params, secret)
+ if err != nil {
+ return api.NewToolCallResult("", fmt.Errorf("failed to create/update ExternalSecret: %w", err)), nil
+ }
+
+ marshalledYaml, err := output.MarshalYaml(result)
+ if err != nil {
+ return api.NewToolCallResult("", fmt.Errorf("failed to marshal result: %w", err)), nil
+ }
+
+ return api.NewToolCallResult(
+ "# ExternalSecret created/updated successfully\n```yaml\n"+marshalledYaml+"```\n\n"+
+ "Use 'external_secrets_sync_status' to verify the secret is syncing correctly.",
+ nil,
+ ), nil
+}
+
+func secretDelete(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
+ name := getStringArg(params, "name", "")
+ if name == "" {
+ return api.NewToolCallResult("", fmt.Errorf("name argument is required")), nil
+ }
+
+ namespace := getStringArg(params, "namespace", "")
+ clusterScoped := getBoolArg(params, "cluster_scoped", false)
+
+ kind := "ExternalSecret"
+ if clusterScoped {
+ kind = "ClusterExternalSecret"
+ namespace = ""
+ }
+
+ gvk := &schema.GroupVersionKind{
+ Group: externalSecretsAPIGroup,
+ Version: "v1",
+ Kind: kind,
+ }
+
+ err := params.ResourcesDelete(params, gvk, namespace, name)
+ if err != nil {
+ return api.NewToolCallResult("", fmt.Errorf("failed to delete %s '%s': %w", kind, name, err)), nil
+ }
+
+ return api.NewToolCallResult(
+ fmt.Sprintf("# %s '%s' deleted successfully\n\nNote: The associated Kubernetes Secret may also be deleted depending on the creationPolicy.", kind, name),
+ nil,
+ ), nil
+}
+
+func secretSyncStatus(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
+ namespace := getStringArg(params, "namespace", "")
+ name := getStringArg(params, "name", "")
+
+ var results []string
+ results = append(results, "# ExternalSecret Sync Status Report\n")
+
+ gvk := &schema.GroupVersionKind{
+ Group: externalSecretsAPIGroup,
+ Version: "v1",
+ Kind: "ExternalSecret",
+ }
+
+ if name != "" {
+ // Get specific ExternalSecret
+ secret, err := params.ResourcesGet(params, gvk, namespace, name)
+ if err != nil {
+ return api.NewToolCallResult("", fmt.Errorf("failed to get ExternalSecret '%s': %w", name, err)), nil
+ }
+ results = append(results, extractSecretSyncStatus(secret, true))
+ } else {
+ // List all ExternalSecrets
+ list, err := params.ResourcesList(params, gvk, namespace, kubernetes.ResourceListOptions{AsTable: false})
+ if err != nil {
+ return api.NewToolCallResult("", fmt.Errorf("failed to list ExternalSecrets: %w", err)), nil
+ }
+ results = append(results, extractSecretListSyncStatus(list))
+ }
+
+ return api.NewToolCallResult(strings.Join(results, "\n"), nil), nil
+}
+
+func secretRefresh(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
+ name := getStringArg(params, "name", "")
+ if name == "" {
+ return api.NewToolCallResult("", fmt.Errorf("name argument is required")), nil
+ }
+
+ namespace := getStringArg(params, "namespace", "")
+
+ gvk := &schema.GroupVersionKind{
+ Group: externalSecretsAPIGroup,
+ Version: "v1",
+ Kind: "ExternalSecret",
+ }
+
+ // Get current ExternalSecret
+ secret, err := params.ResourcesGet(params, gvk, namespace, name)
+ if err != nil {
+ return api.NewToolCallResult("", fmt.Errorf("failed to get ExternalSecret '%s': %w", name, err)), nil
+ }
+
+ // Add/update the force-sync annotation
+ secretMap := secret.UnstructuredContent()
+ metadata, ok := secretMap["metadata"].(map[string]interface{})
+ if !ok {
+ metadata = make(map[string]interface{})
+ secretMap["metadata"] = metadata
+ }
+
+ annotations, ok := metadata["annotations"].(map[string]interface{})
+ if !ok {
+ annotations = make(map[string]interface{})
+ metadata["annotations"] = annotations
+ }
+
+ // Use the reconcile annotation to trigger a sync
+ annotations["force.external-secrets.io/sync"] = fmt.Sprintf("%d", getCurrentTimestamp())
+ secret.SetUnstructuredContent(secretMap)
+
+ // Apply the updated resource
+ secretYaml, _ := output.MarshalYaml(secret)
+ result, err := params.ResourcesCreateOrUpdate(params, secretYaml)
+ if err != nil {
+ return api.NewToolCallResult("", fmt.Errorf("failed to trigger refresh for ExternalSecret '%s': %w", name, err)), nil
+ }
+
+ marshalledYaml, err := output.MarshalYaml(result)
+ if err != nil {
+ return api.NewToolCallResult("", fmt.Errorf("failed to marshal result: %w", err)), nil
+ }
+
+ return api.NewToolCallResult(
+ fmt.Sprintf("# ExternalSecret '%s' refresh triggered\n\n", name)+
+ "The controller will now re-sync the secret from the provider.\n"+
+ "Use 'external_secrets_sync_status' to monitor the sync progress.\n\n"+
+ "```yaml\n"+marshalledYaml+"```",
+ nil,
+ ), nil
+}
+
+// extractSecretSyncStatus extracts sync status from a single ExternalSecret
+func extractSecretSyncStatus(secret interface{}, detailed bool) string {
+ var results []string
+
+ secretMap, ok := secret.(map[string]interface{})
+ if !ok {
+ // Try unstructured.Unstructured
+ type unstructuredLike interface {
+ UnstructuredContent() map[string]interface{}
+ }
+ if u, ok := secret.(unstructuredLike); ok {
+ secretMap = u.UnstructuredContent()
+ }
+ }
+
+ if secretMap == nil {
+ return "No data available"
+ }
+
+ name := "unknown"
+ namespace := "-"
+ if metadata, ok := secretMap["metadata"].(map[string]interface{}); ok {
+ if n, ok := metadata["name"].(string); ok {
+ name = n
+ }
+ if ns, ok := metadata["namespace"].(string); ok {
+ namespace = ns
+ }
+ }
+
+ results = append(results, fmt.Sprintf("## %s (namespace: %s)", name, namespace))
+
+ if statusMap, ok := secretMap["status"].(map[string]interface{}); ok {
+ // Sync status
+ if syncStatus, ok := statusMap["syncedResourceVersion"].(string); ok {
+ results = append(results, fmt.Sprintf("- **Synced Resource Version**: %s", syncStatus))
+ }
+
+ // Refresh time
+ if refreshTime, ok := statusMap["refreshTime"].(string); ok {
+ results = append(results, fmt.Sprintf("- **Last Refresh**: %s", refreshTime))
+ }
+
+ // Binding
+ if binding, ok := statusMap["binding"].(map[string]interface{}); ok {
+ if bindingName, ok := binding["name"].(string); ok {
+ results = append(results, fmt.Sprintf("- **Target Secret**: %s", bindingName))
+ }
+ }
+
+ // Conditions
+ if conditions, ok := statusMap["conditions"].([]interface{}); ok {
+ results = append(results, "\n### Conditions")
+ for _, cond := range conditions {
+ if condMap, ok := cond.(map[string]interface{}); ok {
+ condType := condMap["type"]
+ condStatus := condMap["status"]
+ reason := condMap["reason"]
+ message := condMap["message"]
+ lastTransition := condMap["lastTransitionTime"]
+
+ statusIcon := "❓"
+ switch condStatus {
+ case "True":
+ statusIcon = "✅"
+ case "False":
+ statusIcon = "❌"
+ }
+
+ results = append(results, fmt.Sprintf("- %s **%v** (%v)", statusIcon, condType, reason))
+ if message != nil && message != "" {
+ results = append(results, fmt.Sprintf(" - Message: %v", message))
+ }
+ if detailed && lastTransition != nil {
+ results = append(results, fmt.Sprintf(" - Last Transition: %v", lastTransition))
+ }
+ }
+ }
+ }
+ }
+
+ return strings.Join(results, "\n")
+}
+
+// extractSecretListSyncStatus extracts sync status from a list of ExternalSecrets
+func extractSecretListSyncStatus(list interface{}) string {
+ var results []string
+
+ listMap, ok := list.(map[string]interface{})
+ if !ok {
+ type unstructuredLike interface {
+ UnstructuredContent() map[string]interface{}
+ }
+ if u, ok := list.(unstructuredLike); ok {
+ listMap = u.UnstructuredContent()
+ }
+ }
+
+ if listMap == nil {
+ return "No data available"
+ }
+
+ items, ok := listMap["items"].([]interface{})
+ if !ok || len(items) == 0 {
+ return "No ExternalSecrets found"
+ }
+
+ results = append(results, "| Name | Namespace | Status | Target Secret | Last Refresh |")
+ results = append(results, "|------|-----------|--------|---------------|--------------|")
+
+ for _, item := range items {
+ itemMap, ok := item.(map[string]interface{})
+ if !ok {
+ continue
+ }
+
+ name := "unknown"
+ namespace := "-"
+ status := "Unknown"
+ targetSecret := "-"
+ lastRefresh := "-"
+
+ if metadata, ok := itemMap["metadata"].(map[string]interface{}); ok {
+ if n, ok := metadata["name"].(string); ok {
+ name = n
+ }
+ if ns, ok := metadata["namespace"].(string); ok {
+ namespace = ns
+ }
+ }
+
+ if statusMap, ok := itemMap["status"].(map[string]interface{}); ok {
+ if binding, ok := statusMap["binding"].(map[string]interface{}); ok {
+ if bindingName, ok := binding["name"].(string); ok {
+ targetSecret = bindingName
+ }
+ }
+ if refreshTime, ok := statusMap["refreshTime"].(string); ok {
+ lastRefresh = refreshTime
+ }
+
+ if conditions, ok := statusMap["conditions"].([]interface{}); ok {
+ for _, cond := range conditions {
+ if condMap, ok := cond.(map[string]interface{}); ok {
+ if condType, ok := condMap["type"].(string); ok && condType == "Ready" {
+ if condStatus, ok := condMap["status"].(string); ok {
+ if condStatus == "True" {
+ status = "✅ Synced"
+ } else {
+ status = "❌ Failed"
+ if reason, ok := condMap["reason"].(string); ok {
+ status += " (" + reason + ")"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ results = append(results, fmt.Sprintf("| %s | %s | %s | %s | %s |", name, namespace, status, targetSecret, lastRefresh))
+ }
+
+ return strings.Join(results, "\n")
+}
diff --git a/pkg/toolsets/externalsecrets/status.go b/pkg/toolsets/externalsecrets/status.go
new file mode 100644
index 000000000..0eb53444d
--- /dev/null
+++ b/pkg/toolsets/externalsecrets/status.go
@@ -0,0 +1,1255 @@
+package externalsecrets
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/google/jsonschema-go/jsonschema"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ "k8s.io/utils/ptr"
+
+ "github.com/containers/kubernetes-mcp-server/pkg/api"
+ "github.com/containers/kubernetes-mcp-server/pkg/kubernetes"
+)
+
+func initStatusTools() []api.ServerTool {
+ return []api.ServerTool{
+ {
+ Tool: api.Tool{
+ Name: "external_secrets_debug",
+ Description: `Comprehensive debugging tool for External Secrets Operator issues.
+Collects diagnostic information including:
+- Operator deployment status and logs
+- ExternalSecretsConfig status
+- SecretStore/ClusterSecretStore validation status
+- ExternalSecret sync status and errors
+- Related Kubernetes events
+Use this when troubleshooting sync failures or operator issues.
+Reference: https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html/security_and_compliance/external-secrets-operator-for-red-hat-openshift`,
+ InputSchema: &jsonschema.Schema{
+ Type: "object",
+ Properties: map[string]*jsonschema.Schema{
+ "namespace": {
+ Type: "string",
+ Description: "Namespace to focus debugging on (optional, collects cluster-wide info if not provided)",
+ },
+ "include_logs": {
+ Type: "boolean",
+ Description: "Include operator pod logs in the debug output (default: true)",
+ Default: api.ToRawMessage(true),
+ },
+ "log_tail_lines": {
+ Type: "integer",
+ Description: "Number of log lines to include (default: 50)",
+ Default: api.ToRawMessage(50),
+ },
+ },
+ },
+ Annotations: api.ToolAnnotations{
+ Title: "External Secrets: Debug",
+ ReadOnlyHint: ptr.To(true),
+ DestructiveHint: ptr.To(false),
+ OpenWorldHint: ptr.To(true),
+ },
+ },
+ Handler: debugHandler,
+ },
+ {
+ Tool: api.Tool{
+ Name: "external_secrets_events",
+ Description: `Get Kubernetes events related to External Secrets resources.
+Filters events for ExternalSecret, SecretStore, ClusterSecretStore, and ClusterExternalSecret resources.
+Useful for troubleshooting sync failures and understanding what's happening.`,
+ InputSchema: &jsonschema.Schema{
+ Type: "object",
+ Properties: map[string]*jsonschema.Schema{
+ "namespace": {
+ Type: "string",
+ Description: "Namespace to get events from (optional, gets from all namespaces if not provided)",
+ },
+ "resource_name": {
+ Type: "string",
+ Description: "Filter events for a specific resource name (optional)",
+ },
+ },
+ },
+ Annotations: api.ToolAnnotations{
+ Title: "External Secrets: Events",
+ ReadOnlyHint: ptr.To(true),
+ DestructiveHint: ptr.To(false),
+ OpenWorldHint: ptr.To(true),
+ },
+ },
+ Handler: eventsHandler,
+ },
+ {
+ Tool: api.Tool{
+ Name: "external_secrets_logs",
+ Description: `Get logs from the External Secrets Operator pods.
+Retrieves logs from the operator controller, webhook, and cert-controller pods.
+Useful for diagnosing operator-level issues.`,
+ InputSchema: &jsonschema.Schema{
+ Type: "object",
+ Properties: map[string]*jsonschema.Schema{
+ "tail_lines": {
+ Type: "integer",
+ Description: "Number of lines to retrieve from the end of the logs (default: 100)",
+ Default: api.ToRawMessage(100),
+ },
+ "container": {
+ Type: "string",
+ Description: "Specific container to get logs from (optional, gets all containers if not provided)",
+ },
+ "previous": {
+ Type: "boolean",
+ Description: "Get logs from previous container instance (default: false)",
+ Default: api.ToRawMessage(false),
+ },
+ },
+ },
+ Annotations: api.ToolAnnotations{
+ Title: "External Secrets: Operator Logs",
+ ReadOnlyHint: ptr.To(true),
+ DestructiveHint: ptr.To(false),
+ OpenWorldHint: ptr.To(true),
+ },
+ },
+ Handler: logsHandler,
+ },
+ {
+ Tool: api.Tool{
+ Name: "external_secrets_health",
+ Description: `Quick health check for the External Secrets Operator and resources.
+Returns a summary of:
+- Operator installation status
+- Number of healthy/unhealthy SecretStores
+- Number of synced/failed ExternalSecrets
+- Any critical issues detected
+Use this for a quick overview of the External Secrets health.`,
+ InputSchema: &jsonschema.Schema{
+ Type: "object",
+ Properties: map[string]*jsonschema.Schema{
+ "namespace": {
+ Type: "string",
+ Description: "Namespace to check health for (optional, checks cluster-wide if not provided)",
+ },
+ },
+ },
+ Annotations: api.ToolAnnotations{
+ Title: "External Secrets: Health Check",
+ ReadOnlyHint: ptr.To(true),
+ DestructiveHint: ptr.To(false),
+ OpenWorldHint: ptr.To(true),
+ },
+ },
+ Handler: healthHandler,
+ },
+ {
+ Tool: api.Tool{
+ Name: "external_secrets_guide",
+ Description: `Get guidance and examples for using External Secrets Operator.
+Provides documentation, examples, and best practices for:
+- Setting up different secret providers (AWS, GCP, Azure, Vault, etc.)
+- Creating SecretStores and ExternalSecrets
+- Troubleshooting common issues
+- Security best practices`,
+ InputSchema: &jsonschema.Schema{
+ Type: "object",
+ Properties: map[string]*jsonschema.Schema{
+ "topic": {
+ Type: "string",
+ Description: "Topic to get guidance on: 'providers', 'secretstore', 'externalsecret', 'troubleshooting', 'security', or 'overview' (default: 'overview')",
+ Default: api.ToRawMessage("overview"),
+ Enum: []interface{}{"overview", "providers", "secretstore", "externalsecret", "troubleshooting", "security"},
+ },
+ "provider": {
+ Type: "string",
+ Description: "Specific provider to get examples for: 'aws', 'gcp', 'azure', 'vault', 'kubernetes' (only used when topic is 'providers')",
+ Enum: []interface{}{"aws", "gcp", "azure", "vault", "kubernetes"},
+ },
+ },
+ },
+ Annotations: api.ToolAnnotations{
+ Title: "External Secrets: Guide",
+ ReadOnlyHint: ptr.To(true),
+ DestructiveHint: ptr.To(false),
+ OpenWorldHint: ptr.To(false),
+ },
+ },
+ Handler: guideHandler,
+ },
+ }
+}
+
+func debugHandler(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
+ namespace := getStringArg(params, "namespace", "")
+ includeLogs := getBoolArg(params, "include_logs", true)
+ tailLines := getIntArg(params, "log_tail_lines", 50)
+
+ var results []string
+ results = append(results, "# External Secrets Operator Debug Report\n")
+
+ // 1. Operator Status
+ results = append(results, "## 1. Operator Status")
+ operatorResult, _ := operatorStatus(params)
+ if operatorResult != nil {
+ results = append(results, operatorResult.Content)
+ }
+
+ // 2. SecretStore Validation
+ results = append(results, "\n## 2. SecretStore Validation")
+ storeValidateResult, _ := storeValidate(params)
+ if storeValidateResult != nil {
+ results = append(results, storeValidateResult.Content)
+ }
+
+ // 3. ExternalSecret Sync Status
+ results = append(results, "\n## 3. ExternalSecret Sync Status")
+ syncStatusResult, _ := secretSyncStatus(params)
+ if syncStatusResult != nil {
+ results = append(results, syncStatusResult.Content)
+ }
+
+ // 4. Events
+ results = append(results, "\n## 4. Related Events")
+ eventsResult, _ := eventsHandler(params)
+ if eventsResult != nil {
+ results = append(results, eventsResult.Content)
+ }
+
+ // 5. Operator Logs (if requested)
+ if includeLogs {
+ results = append(results, "\n## 5. Operator Logs")
+ // Get operator pods
+ podGVK := &schema.GroupVersionKind{
+ Group: "",
+ Version: "v1",
+ Kind: "Pod",
+ }
+ pods, err := params.ResourcesList(params, podGVK, externalSecretsOperatorNamespace, kubernetes.ResourceListOptions{
+ ListOptions: metav1.ListOptions{
+ LabelSelector: "app.kubernetes.io/name=external-secrets",
+ },
+ AsTable: false,
+ })
+ if err != nil {
+ results = append(results, fmt.Sprintf("Error getting operator pods: %v", err))
+ } else {
+ podsMap := pods.UnstructuredContent()
+ if items, ok := podsMap["items"].([]interface{}); ok {
+ for _, item := range items {
+ if itemMap, ok := item.(map[string]interface{}); ok {
+ if metadata, ok := itemMap["metadata"].(map[string]interface{}); ok {
+ if podName, ok := metadata["name"].(string); ok {
+ results = append(results, fmt.Sprintf("\n### Pod: %s", podName))
+ logs, err := params.PodsLog(params.Context, externalSecretsOperatorNamespace, podName, "", false, int64(tailLines))
+ if err != nil {
+ results = append(results, fmt.Sprintf("Error getting logs: %v", err))
+ } else {
+ results = append(results, "```")
+ results = append(results, logs)
+ results = append(results, "```")
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // 6. Diagnostic Summary
+ results = append(results, "\n## 6. Diagnostic Summary")
+ results = append(results, "For more detailed troubleshooting, use:")
+ results = append(results, "- `external_secrets_logs` - Get detailed operator logs")
+ results = append(results, "- `external_secrets_store_get` - Inspect specific SecretStore")
+ results = append(results, "- `external_secrets_get` - Inspect specific ExternalSecret")
+ results = append(results, "- `external_secrets_guide topic=troubleshooting` - Get troubleshooting guidance")
+
+ if namespace != "" {
+ results = append(results, fmt.Sprintf("\nNote: Debug focused on namespace: %s", namespace))
+ }
+
+ return api.NewToolCallResult(strings.Join(results, "\n"), nil), nil
+}
+
+func eventsHandler(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
+ namespace := getStringArg(params, "namespace", "")
+ resourceName := getStringArg(params, "resource_name", "")
+
+ eventGVK := &schema.GroupVersionKind{
+ Group: "",
+ Version: "v1",
+ Kind: "Event",
+ }
+
+ events, err := params.ResourcesList(params, eventGVK, namespace, kubernetes.ResourceListOptions{AsTable: false})
+ if err != nil {
+ return api.NewToolCallResult("", fmt.Errorf("failed to list events: %w", err)), nil
+ }
+
+ var results []string
+ results = append(results, "# External Secrets Related Events\n")
+ results = append(results, "| Time | Type | Reason | Object | Message |")
+ results = append(results, "|------|------|--------|--------|---------|")
+
+ eventsMap := events.UnstructuredContent()
+ items, ok := eventsMap["items"].([]interface{})
+ if !ok || len(items) == 0 {
+ return api.NewToolCallResult("No events found", nil), nil
+ }
+
+ externalSecretsKinds := map[string]bool{
+ "ExternalSecret": true,
+ "SecretStore": true,
+ "ClusterSecretStore": true,
+ "ClusterExternalSecret": true,
+ }
+
+ eventCount := 0
+ for _, item := range items {
+ itemMap, ok := item.(map[string]interface{})
+ if !ok {
+ continue
+ }
+
+ // Check if this event is related to External Secrets
+ involvedObject, ok := itemMap["involvedObject"].(map[string]interface{})
+ if !ok {
+ continue
+ }
+
+ kind, _ := involvedObject["kind"].(string)
+ objName, _ := involvedObject["name"].(string)
+
+ if !externalSecretsKinds[kind] {
+ continue
+ }
+
+ if resourceName != "" && objName != resourceName {
+ continue
+ }
+
+ eventType, _ := itemMap["type"].(string)
+ reason, _ := itemMap["reason"].(string)
+ message, _ := itemMap["message"].(string)
+ lastTimestamp, _ := itemMap["lastTimestamp"].(string)
+
+ if len(message) > 60 {
+ message = message[:60] + "..."
+ }
+
+ objectRef := fmt.Sprintf("%s/%s", kind, objName)
+ results = append(results, fmt.Sprintf("| %s | %s | %s | %s | %s |", lastTimestamp, eventType, reason, objectRef, message))
+ eventCount++
+ }
+
+ if eventCount == 0 {
+ return api.NewToolCallResult("No External Secrets related events found", nil), nil
+ }
+
+ return api.NewToolCallResult(strings.Join(results, "\n"), nil), nil
+}
+
+func logsHandler(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
+ tailLines := getIntArg(params, "tail_lines", 100)
+ container := getStringArg(params, "container", "")
+ previous := getBoolArg(params, "previous", false)
+
+ var results []string
+ results = append(results, "# External Secrets Operator Logs\n")
+
+ // Get operator pods
+ podGVK := &schema.GroupVersionKind{
+ Group: "",
+ Version: "v1",
+ Kind: "Pod",
+ }
+
+ pods, err := params.ResourcesList(params, podGVK, externalSecretsOperatorNamespace, kubernetes.ResourceListOptions{
+ AsTable: false,
+ })
+ if err != nil {
+ return api.NewToolCallResult("", fmt.Errorf("failed to list operator pods: %w", err)), nil
+ }
+
+ podsMap := pods.UnstructuredContent()
+ items, ok := podsMap["items"].([]interface{})
+ if !ok || len(items) == 0 {
+ return api.NewToolCallResult("No operator pods found. Is the External Secrets Operator installed?", nil), nil
+ }
+
+ for _, item := range items {
+ itemMap, ok := item.(map[string]interface{})
+ if !ok {
+ continue
+ }
+
+ metadata, ok := itemMap["metadata"].(map[string]interface{})
+ if !ok {
+ continue
+ }
+
+ podName, ok := metadata["name"].(string)
+ if !ok {
+ continue
+ }
+
+ results = append(results, fmt.Sprintf("## Pod: %s", podName))
+
+ logs, err := params.PodsLog(params.Context, externalSecretsOperatorNamespace, podName, container, previous, int64(tailLines))
+ if err != nil {
+ results = append(results, fmt.Sprintf("Error getting logs: %v\n", err))
+ } else if logs == "" {
+ results = append(results, "No logs available\n")
+ } else {
+ results = append(results, "```")
+ results = append(results, logs)
+ results = append(results, "```\n")
+ }
+ }
+
+ return api.NewToolCallResult(strings.Join(results, "\n"), nil), nil
+}
+
+func healthHandler(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
+ namespace := getStringArg(params, "namespace", "")
+
+ var results []string
+ results = append(results, "# External Secrets Health Check\n")
+
+ // Overall status
+ operatorHealthy := true
+ storesHealthy := 0
+ storesUnhealthy := 0
+ secretsSynced := 0
+ secretsFailed := 0
+ var issues []string
+
+ // Check operator
+ results = append(results, "## Operator Status")
+ subscriptionGVK := &schema.GroupVersionKind{
+ Group: operatorsAPIGroup,
+ Version: "v1alpha1",
+ Kind: "Subscription",
+ }
+ _, err := params.ResourcesGet(params, subscriptionGVK, externalSecretsOperatorNamespace, externalSecretsOperatorName)
+ if err != nil {
+ operatorHealthy = false
+ issues = append(issues, "❌ Operator not installed or subscription not found")
+ results = append(results, "- ❌ **Not Installed**")
+ } else {
+ results = append(results, "- ✅ **Installed**")
+
+ // Check if pods are running
+ podGVK := &schema.GroupVersionKind{
+ Group: "",
+ Version: "v1",
+ Kind: "Pod",
+ }
+ pods, err := params.ResourcesList(params, podGVK, externalSecretsOperatorNamespace, kubernetes.ResourceListOptions{AsTable: false})
+ if err == nil {
+ podsMap := pods.UnstructuredContent()
+ if items, ok := podsMap["items"].([]interface{}); ok && len(items) > 0 {
+ runningPods := 0
+ for _, item := range items {
+ if itemMap, ok := item.(map[string]interface{}); ok {
+ if status, ok := itemMap["status"].(map[string]interface{}); ok {
+ if phase, ok := status["phase"].(string); ok && phase == "Running" {
+ runningPods++
+ }
+ }
+ }
+ }
+ results = append(results, fmt.Sprintf("- ✅ **%d pods running**", runningPods))
+ }
+ }
+ }
+
+ // Check SecretStores
+ results = append(results, "\n## SecretStore Status")
+ secretStoreGVK := &schema.GroupVersionKind{
+ Group: externalSecretsAPIGroup,
+ Version: "v1",
+ Kind: "SecretStore",
+ }
+ stores, err := params.ResourcesList(params, secretStoreGVK, namespace, kubernetes.ResourceListOptions{AsTable: false})
+ if err == nil {
+ storesMap := stores.UnstructuredContent()
+ if items, ok := storesMap["items"].([]interface{}); ok {
+ for _, item := range items {
+ if itemMap, ok := item.(map[string]interface{}); ok {
+ if status, ok := itemMap["status"].(map[string]interface{}); ok {
+ if conditions, ok := status["conditions"].([]interface{}); ok {
+ for _, cond := range conditions {
+ if condMap, ok := cond.(map[string]interface{}); ok {
+ if condType, ok := condMap["type"].(string); ok && condType == "Ready" {
+ if condStatus, ok := condMap["status"].(string); ok {
+ if condStatus == "True" {
+ storesHealthy++
+ } else {
+ storesUnhealthy++
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Also check ClusterSecretStores
+ clusterStoreGVK := &schema.GroupVersionKind{
+ Group: externalSecretsAPIGroup,
+ Version: "v1",
+ Kind: "ClusterSecretStore",
+ }
+ clusterStores, err := params.ResourcesList(params, clusterStoreGVK, "", kubernetes.ResourceListOptions{AsTable: false})
+ if err == nil {
+ storesMap := clusterStores.UnstructuredContent()
+ if items, ok := storesMap["items"].([]interface{}); ok {
+ for _, item := range items {
+ if itemMap, ok := item.(map[string]interface{}); ok {
+ if status, ok := itemMap["status"].(map[string]interface{}); ok {
+ if conditions, ok := status["conditions"].([]interface{}); ok {
+ for _, cond := range conditions {
+ if condMap, ok := cond.(map[string]interface{}); ok {
+ if condType, ok := condMap["type"].(string); ok && condType == "Ready" {
+ if condStatus, ok := condMap["status"].(string); ok {
+ if condStatus == "True" {
+ storesHealthy++
+ } else {
+ storesUnhealthy++
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ results = append(results, fmt.Sprintf("- ✅ Healthy: %d", storesHealthy))
+ results = append(results, fmt.Sprintf("- ❌ Unhealthy: %d", storesUnhealthy))
+ if storesUnhealthy > 0 {
+ issues = append(issues, fmt.Sprintf("❌ %d SecretStore(s) have validation issues", storesUnhealthy))
+ }
+
+ // Check ExternalSecrets
+ results = append(results, "\n## ExternalSecret Status")
+ externalSecretGVK := &schema.GroupVersionKind{
+ Group: externalSecretsAPIGroup,
+ Version: "v1",
+ Kind: "ExternalSecret",
+ }
+ secrets, err := params.ResourcesList(params, externalSecretGVK, namespace, kubernetes.ResourceListOptions{AsTable: false})
+ if err == nil {
+ secretsMap := secrets.UnstructuredContent()
+ if items, ok := secretsMap["items"].([]interface{}); ok {
+ for _, item := range items {
+ if itemMap, ok := item.(map[string]interface{}); ok {
+ if status, ok := itemMap["status"].(map[string]interface{}); ok {
+ if conditions, ok := status["conditions"].([]interface{}); ok {
+ for _, cond := range conditions {
+ if condMap, ok := cond.(map[string]interface{}); ok {
+ if condType, ok := condMap["type"].(string); ok && condType == "Ready" {
+ if condStatus, ok := condMap["status"].(string); ok {
+ if condStatus == "True" {
+ secretsSynced++
+ } else {
+ secretsFailed++
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ results = append(results, fmt.Sprintf("- ✅ Synced: %d", secretsSynced))
+ results = append(results, fmt.Sprintf("- ❌ Failed: %d", secretsFailed))
+ if secretsFailed > 0 {
+ issues = append(issues, fmt.Sprintf("❌ %d ExternalSecret(s) have sync issues", secretsFailed))
+ }
+
+ // Summary
+ results = append(results, "\n## Summary")
+ if operatorHealthy && storesUnhealthy == 0 && secretsFailed == 0 {
+ results = append(results, "✅ **All systems healthy**")
+ } else {
+ results = append(results, "⚠️ **Issues detected:**")
+ for _, issue := range issues {
+ results = append(results, fmt.Sprintf("- %s", issue))
+ }
+ results = append(results, "\nUse `external_secrets_debug` for detailed troubleshooting.")
+ }
+
+ return api.NewToolCallResult(strings.Join(results, "\n"), nil), nil
+}
+
+func guideHandler(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
+ topic := getStringArg(params, "topic", "overview")
+ provider := getStringArg(params, "provider", "")
+
+ switch topic {
+ case "overview":
+ return api.NewToolCallResult(guideOverview(), nil), nil
+ case "providers":
+ return api.NewToolCallResult(guideProviders(provider), nil), nil
+ case "secretstore":
+ return api.NewToolCallResult(guideSecretStore(), nil), nil
+ case "externalsecret":
+ return api.NewToolCallResult(guideExternalSecret(), nil), nil
+ case "troubleshooting":
+ return api.NewToolCallResult(guideTroubleshooting(), nil), nil
+ case "security":
+ return api.NewToolCallResult(guideSecurity(), nil), nil
+ default:
+ return api.NewToolCallResult(guideOverview(), nil), nil
+ }
+}
+
+func guideOverview() string {
+ return `# External Secrets Operator Overview
+
+The External Secrets Operator (ESO) synchronizes secrets from external APIs (AWS Secrets Manager,
+HashiCorp Vault, Google Secret Manager, Azure Key Vault, etc.) into Kubernetes Secrets.
+
+## Key Concepts
+
+### SecretStore / ClusterSecretStore
+Defines how to connect to an external secret provider. SecretStore is namespaced,
+ClusterSecretStore is cluster-wide.
+
+### ExternalSecret / ClusterExternalSecret
+Defines what secrets to fetch and how to create the Kubernetes Secret.
+
+## Quick Start
+
+1. **Install the operator:**
+ ` + "`external_secrets_operator_install`" + `
+
+2. **Create a SecretStore:**
+ ` + "`external_secrets_store_create`" + ` with your provider configuration
+
+3. **Create an ExternalSecret:**
+ ` + "`external_secrets_create`" + ` to sync secrets from the provider
+
+4. **Verify sync status:**
+ ` + "`external_secrets_sync_status`" + `
+
+## Available Tools
+
+- ` + "`external_secrets_operator_install/status/uninstall`" + ` - Operator lifecycle
+- ` + "`external_secrets_config_get/apply`" + ` - Operator configuration
+- ` + "`external_secrets_store_list/get/create/delete/validate`" + ` - SecretStore management
+- ` + "`external_secrets_list/get/create/delete/sync_status/refresh`" + ` - ExternalSecret management
+- ` + "`external_secrets_debug/events/logs/health`" + ` - Debugging and monitoring
+- ` + "`external_secrets_guide`" + ` - Documentation and examples
+
+## References
+
+- [Red Hat Documentation](https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html/security_and_compliance/external-secrets-operator-for-red-hat-openshift)
+- [External Secrets Documentation](https://external-secrets.io/latest/)
+`
+}
+
+func guideProviders(provider string) string {
+ switch provider {
+ case "aws":
+ return guideProviderAWS()
+ case "gcp":
+ return guideProviderGCP()
+ case "azure":
+ return guideProviderAzure()
+ case "vault":
+ return guideProviderVault()
+ case "kubernetes":
+ return guideProviderKubernetes()
+ default:
+ return `# Supported Providers
+
+External Secrets Operator supports many secret providers:
+
+## Cloud Providers
+- **AWS Secrets Manager** - Use ` + "`external_secrets_guide topic=providers provider=aws`" + `
+- **AWS Parameter Store**
+- **Google Cloud Secret Manager** - Use ` + "`external_secrets_guide topic=providers provider=gcp`" + `
+- **Azure Key Vault** - Use ` + "`external_secrets_guide topic=providers provider=azure`" + `
+
+## Secret Management Tools
+- **HashiCorp Vault** - Use ` + "`external_secrets_guide topic=providers provider=vault`" + `
+- **CyberArk Conjur**
+- **Bitwarden**
+- **1Password**
+- **Doppler**
+- **Infisical**
+
+## Other
+- **Kubernetes Secrets** - Use ` + "`external_secrets_guide topic=providers provider=kubernetes`" + `
+- **Webhook** - Custom HTTP endpoints
+
+For detailed provider setup, specify the provider parameter.
+`
+ }
+}
+
+func guideProviderAWS() string {
+ return `# AWS Secrets Manager Setup
+
+## Prerequisites
+- AWS credentials with access to Secrets Manager
+- IAM policy allowing secretsmanager:GetSecretValue
+
+## SecretStore Example
+
+` + "```yaml" + `
+apiVersion: external-secrets.io/v1
+kind: SecretStore
+metadata:
+ name: aws-secretsmanager
+ namespace: my-namespace
+spec:
+ provider:
+ aws:
+ service: SecretsManager
+ region: us-east-1
+ auth:
+ secretRef:
+ accessKeyIDSecretRef:
+ name: aws-credentials
+ key: access-key
+ secretAccessKeySecretRef:
+ name: aws-credentials
+ key: secret-access-key
+` + "```" + `
+
+## Create AWS credentials secret first:
+
+` + "```yaml" + `
+apiVersion: v1
+kind: Secret
+metadata:
+ name: aws-credentials
+ namespace: my-namespace
+type: Opaque
+stringData:
+ access-key: AKIAIOSFODNN7EXAMPLE
+ secret-access-key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
+` + "```" + `
+
+## ExternalSecret Example
+
+` + "```yaml" + `
+apiVersion: external-secrets.io/v1
+kind: ExternalSecret
+metadata:
+ name: my-secret
+ namespace: my-namespace
+spec:
+ refreshInterval: 1h
+ secretStoreRef:
+ name: aws-secretsmanager
+ kind: SecretStore
+ target:
+ name: my-k8s-secret
+ data:
+ - secretKey: password
+ remoteRef:
+ key: my-aws-secret
+ property: password
+` + "```" + `
+
+## Using IAM Roles for Service Accounts (IRSA)
+
+For EKS clusters, you can use IRSA instead of static credentials:
+
+` + "```yaml" + `
+apiVersion: external-secrets.io/v1
+kind: SecretStore
+metadata:
+ name: aws-secretsmanager-irsa
+spec:
+ provider:
+ aws:
+ service: SecretsManager
+ region: us-east-1
+ auth:
+ jwt:
+ serviceAccountRef:
+ name: my-service-account
+` + "```" + `
+`
+}
+
+func guideProviderGCP() string {
+ return `# Google Cloud Secret Manager Setup
+
+## Prerequisites
+- GCP Service Account with Secret Manager Secret Accessor role
+- Service account key JSON
+
+## SecretStore Example
+
+` + "```yaml" + `
+apiVersion: external-secrets.io/v1
+kind: SecretStore
+metadata:
+ name: gcp-secretmanager
+ namespace: my-namespace
+spec:
+ provider:
+ gcpsm:
+ projectID: my-gcp-project
+ auth:
+ secretRef:
+ secretAccessKeySecretRef:
+ name: gcp-credentials
+ key: secret-access-credentials
+` + "```" + `
+
+## Create GCP credentials secret:
+
+` + "```yaml" + `
+apiVersion: v1
+kind: Secret
+metadata:
+ name: gcp-credentials
+ namespace: my-namespace
+type: Opaque
+stringData:
+ secret-access-credentials: |
+ {
+ "type": "service_account",
+ "project_id": "my-gcp-project",
+ ...
+ }
+` + "```" + `
+
+## ExternalSecret Example
+
+` + "```yaml" + `
+apiVersion: external-secrets.io/v1
+kind: ExternalSecret
+metadata:
+ name: my-secret
+ namespace: my-namespace
+spec:
+ refreshInterval: 1h
+ secretStoreRef:
+ name: gcp-secretmanager
+ kind: SecretStore
+ target:
+ name: my-k8s-secret
+ data:
+ - secretKey: api-key
+ remoteRef:
+ key: my-gcp-secret
+` + "```" + `
+`
+}
+
+func guideProviderAzure() string {
+ return `# Azure Key Vault Setup
+
+## Prerequisites
+- Azure Key Vault instance
+- Service Principal or Managed Identity with access
+
+## SecretStore Example (Service Principal)
+
+` + "```yaml" + `
+apiVersion: external-secrets.io/v1
+kind: SecretStore
+metadata:
+ name: azure-keyvault
+ namespace: my-namespace
+spec:
+ provider:
+ azurekv:
+ vaultUrl: https://my-keyvault.vault.azure.net
+ authType: ServicePrincipal
+ tenantId: "00000000-0000-0000-0000-000000000000"
+ authSecretRef:
+ clientId:
+ name: azure-credentials
+ key: client-id
+ clientSecret:
+ name: azure-credentials
+ key: client-secret
+` + "```" + `
+
+## ExternalSecret Example
+
+` + "```yaml" + `
+apiVersion: external-secrets.io/v1
+kind: ExternalSecret
+metadata:
+ name: my-secret
+ namespace: my-namespace
+spec:
+ refreshInterval: 1h
+ secretStoreRef:
+ name: azure-keyvault
+ kind: SecretStore
+ target:
+ name: my-k8s-secret
+ data:
+ - secretKey: connection-string
+ remoteRef:
+ key: database-connection-string
+` + "```" + `
+`
+}
+
+func guideProviderVault() string {
+ return `# HashiCorp Vault Setup
+
+## Prerequisites
+- HashiCorp Vault instance
+- Vault token or Kubernetes auth configured
+
+## SecretStore Example (Token Auth)
+
+` + "```yaml" + `
+apiVersion: external-secrets.io/v1
+kind: SecretStore
+metadata:
+ name: vault
+ namespace: my-namespace
+spec:
+ provider:
+ vault:
+ server: https://vault.example.com
+ path: secret
+ version: v2
+ auth:
+ tokenSecretRef:
+ name: vault-token
+ key: token
+` + "```" + `
+
+## SecretStore Example (Kubernetes Auth)
+
+` + "```yaml" + `
+apiVersion: external-secrets.io/v1
+kind: SecretStore
+metadata:
+ name: vault-k8s
+ namespace: my-namespace
+spec:
+ provider:
+ vault:
+ server: https://vault.example.com
+ path: secret
+ version: v2
+ auth:
+ kubernetes:
+ mountPath: kubernetes
+ role: my-role
+ serviceAccountRef:
+ name: vault-auth
+` + "```" + `
+
+## ExternalSecret Example
+
+` + "```yaml" + `
+apiVersion: external-secrets.io/v1
+kind: ExternalSecret
+metadata:
+ name: my-secret
+ namespace: my-namespace
+spec:
+ refreshInterval: 1h
+ secretStoreRef:
+ name: vault
+ kind: SecretStore
+ target:
+ name: my-k8s-secret
+ data:
+ - secretKey: password
+ remoteRef:
+ key: secret/data/myapp
+ property: password
+` + "```" + `
+`
+}
+
+func guideProviderKubernetes() string {
+ return `# Kubernetes Secrets Provider
+
+The Kubernetes provider allows you to sync secrets from one namespace/cluster to another.
+
+## SecretStore Example
+
+` + "```yaml" + `
+apiVersion: external-secrets.io/v1
+kind: SecretStore
+metadata:
+ name: kubernetes-store
+ namespace: my-namespace
+spec:
+ provider:
+ kubernetes:
+ remoteNamespace: source-namespace
+ auth:
+ serviceAccount:
+ name: secret-reader
+ server:
+ caProvider:
+ type: ConfigMap
+ name: kube-root-ca.crt
+ key: ca.crt
+` + "```" + `
+
+## ExternalSecret Example
+
+` + "```yaml" + `
+apiVersion: external-secrets.io/v1
+kind: ExternalSecret
+metadata:
+ name: copied-secret
+ namespace: my-namespace
+spec:
+ refreshInterval: 1h
+ secretStoreRef:
+ name: kubernetes-store
+ kind: SecretStore
+ target:
+ name: my-copied-secret
+ dataFrom:
+ - extract:
+ key: source-secret-name
+` + "```" + `
+`
+}
+
+func guideSecretStore() string {
+ return `# SecretStore Guide
+
+## SecretStore vs ClusterSecretStore
+
+- **SecretStore**: Namespaced, can only be used by ExternalSecrets in the same namespace
+- **ClusterSecretStore**: Cluster-scoped, can be used by ExternalSecrets in any namespace
+
+## Best Practices
+
+1. **Use ClusterSecretStore for shared providers**
+ When multiple namespaces need access to the same provider
+
+2. **Use SecretStore for namespace isolation**
+ When different namespaces should have different access levels
+
+3. **Store credentials in the same namespace**
+ SecretStore auth credentials should be in the same namespace
+
+4. **Use conditions to verify store health**
+ Always check the Ready condition before using
+
+## Validation
+
+After creating a SecretStore, verify it's valid:
+
+` + "```bash" + `
+# Using MCP tool
+external_secrets_store_validate
+
+# The store should show:
+# - Status: Valid
+# - Capabilities: ReadWrite or ReadOnly
+` + "```" + `
+
+## Common Issues
+
+1. **Authentication failures**: Check credentials are correct
+2. **Network issues**: Ensure the cluster can reach the provider
+3. **Permission issues**: Verify IAM/RBAC permissions
+`
+}
+
+func guideExternalSecret() string {
+ return `# ExternalSecret Guide
+
+## Key Fields
+
+- **refreshInterval**: How often to sync (e.g., "1h", "15m")
+- **secretStoreRef**: Reference to the SecretStore to use
+- **target**: Configuration for the created Kubernetes Secret
+- **data**: List of individual secret key mappings
+- **dataFrom**: Extract multiple keys at once
+
+## Data vs DataFrom
+
+### data - Individual key mapping
+` + "```yaml" + `
+data:
+- secretKey: my-key # Key in K8s Secret
+ remoteRef:
+ key: remote-secret # Secret name in provider
+ property: password # Specific property (if JSON)
+` + "```" + `
+
+### dataFrom - Extract all keys
+` + "```yaml" + `
+dataFrom:
+- extract:
+ key: remote-secret # Extracts all keys from this secret
+` + "```" + `
+
+## Creation Policies
+
+- **Owner** (default): Secret is deleted when ExternalSecret is deleted
+- **Orphan**: Secret is kept when ExternalSecret is deleted
+- **Merge**: Merge with existing secret
+- **None**: Don't create, only sync to existing secret
+
+## Template Support
+
+You can transform secret data:
+
+` + "```yaml" + `
+target:
+ name: my-secret
+ template:
+ type: kubernetes.io/dockerconfigjson
+ data:
+ .dockerconfigjson: |
+ {"auths":{"registry.example.com":{"auth":"{{ .username }}:{{ .password | b64enc }}"}}}
+` + "```" + `
+`
+}
+
+func guideTroubleshooting() string {
+ return `# Troubleshooting Guide
+
+## Common Issues
+
+### 1. SecretStore Not Ready
+
+**Symptoms**: SecretStore shows status "Invalid" or not Ready
+
+**Debug steps**:
+` + "```" + `
+external_secrets_store_get name= namespace=
+external_secrets_events namespace=
+` + "```" + `
+
+**Common causes**:
+- Invalid credentials
+- Network connectivity issues
+- Wrong region/endpoint
+- Missing IAM permissions
+
+### 2. ExternalSecret Not Syncing
+
+**Symptoms**: ExternalSecret shows "SecretSyncedError" or stuck
+
+**Debug steps**:
+` + "```" + `
+external_secrets_sync_status name= namespace=
+external_secrets_debug namespace=
+` + "```" + `
+
+**Common causes**:
+- SecretStore not ready
+- Secret doesn't exist in provider
+- Wrong key/property path
+- Permission denied to specific secret
+
+### 3. Operator Not Running
+
+**Symptoms**: No pods in external-secrets-operator namespace
+
+**Debug steps**:
+` + "```" + `
+external_secrets_operator_status
+external_secrets_logs
+` + "```" + `
+
+**Common causes**:
+- Subscription not approved (Manual approval mode)
+- Resource constraints
+- Image pull errors
+
+## Quick Health Check
+
+` + "```" + `
+external_secrets_health
+` + "```" + `
+
+## Comprehensive Debug
+
+` + "```" + `
+external_secrets_debug namespace= include_logs=true
+` + "```" + `
+
+## Force Refresh
+
+If a secret should have new data:
+` + "```" + `
+external_secrets_refresh name= namespace=
+` + "```" + `
+`
+}
+
+func guideSecurity() string {
+ return `# Security Best Practices
+
+## 1. Principle of Least Privilege
+
+- Create separate SecretStores per namespace/team
+- Use specific IAM policies that only allow access to needed secrets
+- Use ClusterSecretStore sparingly
+
+## 2. Secure Credential Storage
+
+- Store provider credentials in Kubernetes Secrets
+- Use workload identity when possible (IRSA, Workload Identity)
+- Rotate credentials regularly
+
+## 3. Network Security
+
+- Use VPC endpoints for cloud providers
+- Configure network policies for the operator namespace
+- Use TLS for all provider connections
+
+## 4. RBAC Configuration
+
+Limit who can create/modify SecretStores:
+
+` + "```yaml" + `
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: external-secrets-user
+rules:
+- apiGroups: ["external-secrets.io"]
+ resources: ["externalsecrets"]
+ verbs: ["get", "list", "create", "update", "delete"]
+# Note: SecretStore creation should be restricted to admins
+` + "```" + `
+
+## 5. Audit and Monitoring
+
+- Enable audit logging for external-secrets resources
+- Monitor sync failures with ` + "`external_secrets_health`" + `
+- Set up alerts for sync errors
+
+## 6. Secret Rotation
+
+- Set appropriate refreshInterval (not too frequent, not too rare)
+- Use provider-side rotation when available
+- Test rotation procedures regularly
+`
+}
diff --git a/pkg/toolsets/externalsecrets/stores.go b/pkg/toolsets/externalsecrets/stores.go
new file mode 100644
index 000000000..7caf9c166
--- /dev/null
+++ b/pkg/toolsets/externalsecrets/stores.go
@@ -0,0 +1,444 @@
+package externalsecrets
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/google/jsonschema-go/jsonschema"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ "k8s.io/utils/ptr"
+
+ "github.com/containers/kubernetes-mcp-server/pkg/api"
+ "github.com/containers/kubernetes-mcp-server/pkg/kubernetes"
+ "github.com/containers/kubernetes-mcp-server/pkg/output"
+)
+
+const (
+ externalSecretsAPIGroup = "external-secrets.io"
+)
+
+func initStoreTools() []api.ServerTool {
+ return []api.ServerTool{
+ {
+ Tool: api.Tool{
+ Name: "external_secrets_store_list",
+ Description: `List SecretStores and/or ClusterSecretStores in the cluster.
+SecretStore is a namespaced resource that specifies how to access a secret provider (AWS, GCP, Azure, Vault, etc.).
+ClusterSecretStore is a cluster-scoped variant that can be referenced from any namespace.
+Reference: https://external-secrets.io/latest/api/secretstore/`,
+ InputSchema: &jsonschema.Schema{
+ Type: "object",
+ Properties: map[string]*jsonschema.Schema{
+ "namespace": {
+ Type: "string",
+ Description: "Namespace to list SecretStores from (optional, lists from all namespaces if not provided)",
+ },
+ "cluster_scoped": {
+ Type: "boolean",
+ Description: "If true, list ClusterSecretStores instead of SecretStores (default: false)",
+ Default: api.ToRawMessage(false),
+ },
+ },
+ },
+ Annotations: api.ToolAnnotations{
+ Title: "External Secrets: List Stores",
+ ReadOnlyHint: ptr.To(true),
+ DestructiveHint: ptr.To(false),
+ OpenWorldHint: ptr.To(true),
+ },
+ },
+ Handler: storeList,
+ },
+ {
+ Tool: api.Tool{
+ Name: "external_secrets_store_get",
+ Description: `Get details of a SecretStore or ClusterSecretStore.
+Returns the full specification and current status including validation state and capabilities.
+Reference: https://external-secrets.io/latest/api/secretstore/`,
+ InputSchema: &jsonschema.Schema{
+ Type: "object",
+ Properties: map[string]*jsonschema.Schema{
+ "name": {
+ Type: "string",
+ Description: "Name of the SecretStore or ClusterSecretStore",
+ },
+ "namespace": {
+ Type: "string",
+ Description: "Namespace of the SecretStore (not needed for ClusterSecretStore)",
+ },
+ "cluster_scoped": {
+ Type: "boolean",
+ Description: "If true, get a ClusterSecretStore instead of SecretStore (default: false)",
+ Default: api.ToRawMessage(false),
+ },
+ },
+ Required: []string{"name"},
+ },
+ Annotations: api.ToolAnnotations{
+ Title: "External Secrets: Get Store",
+ ReadOnlyHint: ptr.To(true),
+ DestructiveHint: ptr.To(false),
+ OpenWorldHint: ptr.To(true),
+ },
+ },
+ Handler: storeGet,
+ },
+ {
+ Tool: api.Tool{
+ Name: "external_secrets_store_create",
+ Description: `Create or update a SecretStore or ClusterSecretStore.
+Supports various providers: AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, HashiCorp Vault,
+Kubernetes Secrets, Bitwarden, 1Password, and many more.
+
+Example SecretStore for AWS Secrets Manager:
+ apiVersion: external-secrets.io/v1
+ kind: SecretStore
+ metadata:
+ name: aws-secretsmanager
+ namespace: my-namespace
+ spec:
+ provider:
+ aws:
+ service: SecretsManager
+ region: us-east-1
+ auth:
+ secretRef:
+ accessKeyIDSecretRef:
+ name: aws-credentials
+ key: access-key
+ secretAccessKeySecretRef:
+ name: aws-credentials
+ key: secret-access-key
+
+Reference: https://external-secrets.io/latest/provider/aws-secrets-manager/`,
+ InputSchema: &jsonschema.Schema{
+ Type: "object",
+ Properties: map[string]*jsonschema.Schema{
+ "store": {
+ Type: "string",
+ Description: "YAML or JSON representation of the SecretStore or ClusterSecretStore resource",
+ },
+ },
+ Required: []string{"store"},
+ },
+ Annotations: api.ToolAnnotations{
+ Title: "External Secrets: Create/Update Store",
+ ReadOnlyHint: ptr.To(false),
+ DestructiveHint: ptr.To(false),
+ IdempotentHint: ptr.To(true),
+ OpenWorldHint: ptr.To(true),
+ },
+ },
+ Handler: storeCreate,
+ },
+ {
+ Tool: api.Tool{
+ Name: "external_secrets_store_delete",
+ Description: `Delete a SecretStore or ClusterSecretStore.
+WARNING: Deleting a store will cause ExternalSecrets referencing it to fail syncing.`,
+ InputSchema: &jsonschema.Schema{
+ Type: "object",
+ Properties: map[string]*jsonschema.Schema{
+ "name": {
+ Type: "string",
+ Description: "Name of the SecretStore or ClusterSecretStore to delete",
+ },
+ "namespace": {
+ Type: "string",
+ Description: "Namespace of the SecretStore (not needed for ClusterSecretStore)",
+ },
+ "cluster_scoped": {
+ Type: "boolean",
+ Description: "If true, delete a ClusterSecretStore instead of SecretStore (default: false)",
+ Default: api.ToRawMessage(false),
+ },
+ },
+ Required: []string{"name"},
+ },
+ Annotations: api.ToolAnnotations{
+ Title: "External Secrets: Delete Store",
+ ReadOnlyHint: ptr.To(false),
+ DestructiveHint: ptr.To(true),
+ IdempotentHint: ptr.To(true),
+ OpenWorldHint: ptr.To(true),
+ },
+ },
+ Handler: storeDelete,
+ },
+ {
+ Tool: api.Tool{
+ Name: "external_secrets_store_validate",
+ Description: `Check the validation status of SecretStores and/or ClusterSecretStores.
+Returns a summary of store health including:
+- Whether the store is valid and ready
+- Capabilities (ReadOnly, ReadWrite)
+- Any error conditions
+Use this to quickly identify stores with configuration issues.`,
+ InputSchema: &jsonschema.Schema{
+ Type: "object",
+ Properties: map[string]*jsonschema.Schema{
+ "namespace": {
+ Type: "string",
+ Description: "Namespace to check SecretStores in (optional, checks all namespaces if not provided)",
+ },
+ "include_cluster_stores": {
+ Type: "boolean",
+ Description: "Also include ClusterSecretStores in the validation check (default: true)",
+ Default: api.ToRawMessage(true),
+ },
+ },
+ },
+ Annotations: api.ToolAnnotations{
+ Title: "External Secrets: Validate Stores",
+ ReadOnlyHint: ptr.To(true),
+ DestructiveHint: ptr.To(false),
+ OpenWorldHint: ptr.To(true),
+ },
+ },
+ Handler: storeValidate,
+ },
+ }
+}
+
+func storeList(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
+ namespace := getStringArg(params, "namespace", "")
+ clusterScoped := getBoolArg(params, "cluster_scoped", false)
+
+ kind := "SecretStore"
+ if clusterScoped {
+ kind = "ClusterSecretStore"
+ namespace = "" // ClusterSecretStore is cluster-scoped
+ }
+
+ gvk := &schema.GroupVersionKind{
+ Group: externalSecretsAPIGroup,
+ Version: "v1",
+ Kind: kind,
+ }
+
+ list, err := params.ResourcesList(params, gvk, namespace, kubernetes.ResourceListOptions{AsTable: true})
+ if err != nil {
+ return api.NewToolCallResult("", fmt.Errorf("failed to list %s: %w", kind, err)), nil
+ }
+
+ result, _ := params.ListOutput.PrintObj(list)
+ title := fmt.Sprintf("# %s List", kind)
+ if namespace != "" {
+ title += fmt.Sprintf(" (namespace: %s)", namespace)
+ }
+
+ return api.NewToolCallResult(title+"\n"+result, nil), nil
+}
+
+func storeGet(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
+ name := getStringArg(params, "name", "")
+ if name == "" {
+ return api.NewToolCallResult("", fmt.Errorf("name argument is required")), nil
+ }
+
+ namespace := getStringArg(params, "namespace", "")
+ clusterScoped := getBoolArg(params, "cluster_scoped", false)
+
+ kind := "SecretStore"
+ if clusterScoped {
+ kind = "ClusterSecretStore"
+ namespace = ""
+ }
+
+ gvk := &schema.GroupVersionKind{
+ Group: externalSecretsAPIGroup,
+ Version: "v1",
+ Kind: kind,
+ }
+
+ store, err := params.ResourcesGet(params, gvk, namespace, name)
+ if err != nil {
+ return api.NewToolCallResult("", fmt.Errorf("failed to get %s '%s': %w", kind, name, err)), nil
+ }
+
+ storeYaml, _ := output.MarshalYaml(store)
+ return api.NewToolCallResult(
+ fmt.Sprintf("# %s: %s\n```yaml\n%s```", kind, name, storeYaml),
+ nil,
+ ), nil
+}
+
+func storeCreate(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
+ store, ok := params.GetArguments()["store"].(string)
+ if !ok || store == "" {
+ return api.NewToolCallResult("", fmt.Errorf("store argument is required")), nil
+ }
+
+ result, err := params.ResourcesCreateOrUpdate(params, store)
+ if err != nil {
+ return api.NewToolCallResult("", fmt.Errorf("failed to create/update store: %w", err)), nil
+ }
+
+ marshalledYaml, err := output.MarshalYaml(result)
+ if err != nil {
+ return api.NewToolCallResult("", fmt.Errorf("failed to marshal result: %w", err)), nil
+ }
+
+ return api.NewToolCallResult(
+ "# Store created/updated successfully\n```yaml\n"+marshalledYaml+"```\n\n"+
+ "Use 'external_secrets_store_validate' to verify the store is working correctly.",
+ nil,
+ ), nil
+}
+
+func storeDelete(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
+ name := getStringArg(params, "name", "")
+ if name == "" {
+ return api.NewToolCallResult("", fmt.Errorf("name argument is required")), nil
+ }
+
+ namespace := getStringArg(params, "namespace", "")
+ clusterScoped := getBoolArg(params, "cluster_scoped", false)
+
+ kind := "SecretStore"
+ if clusterScoped {
+ kind = "ClusterSecretStore"
+ namespace = ""
+ }
+
+ gvk := &schema.GroupVersionKind{
+ Group: externalSecretsAPIGroup,
+ Version: "v1",
+ Kind: kind,
+ }
+
+ err := params.ResourcesDelete(params, gvk, namespace, name)
+ if err != nil {
+ return api.NewToolCallResult("", fmt.Errorf("failed to delete %s '%s': %w", kind, name, err)), nil
+ }
+
+ return api.NewToolCallResult(
+ fmt.Sprintf("# %s '%s' deleted successfully", kind, name),
+ nil,
+ ), nil
+}
+
+func storeValidate(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
+ namespace := getStringArg(params, "namespace", "")
+ includeClusterStores := getBoolArg(params, "include_cluster_stores", true)
+
+ var results []string
+ results = append(results, "# SecretStore Validation Report\n")
+
+ // Check SecretStores
+ secretStoreGVK := &schema.GroupVersionKind{
+ Group: externalSecretsAPIGroup,
+ Version: "v1",
+ Kind: "SecretStore",
+ }
+
+ storeList, err := params.ResourcesList(params, secretStoreGVK, namespace, kubernetes.ResourceListOptions{AsTable: false})
+ if err != nil {
+ results = append(results, fmt.Sprintf("## SecretStores\nError listing: %v\n", err))
+ } else {
+ storeResults := extractStoreStatus(storeList, "SecretStore")
+ results = append(results, storeResults)
+ }
+
+ // Check ClusterSecretStores
+ if includeClusterStores {
+ clusterStoreGVK := &schema.GroupVersionKind{
+ Group: externalSecretsAPIGroup,
+ Version: "v1",
+ Kind: "ClusterSecretStore",
+ }
+
+ clusterStoreList, err := params.ResourcesList(params, clusterStoreGVK, "", kubernetes.ResourceListOptions{AsTable: false})
+ if err != nil {
+ results = append(results, fmt.Sprintf("## ClusterSecretStores\nError listing: %v\n", err))
+ } else {
+ clusterStoreResults := extractStoreStatus(clusterStoreList, "ClusterSecretStore")
+ results = append(results, clusterStoreResults)
+ }
+ }
+
+ return api.NewToolCallResult(strings.Join(results, "\n"), nil), nil
+}
+
+// extractStoreStatus processes a list of stores and returns a formatted status summary
+func extractStoreStatus(list interface{}, kind string) string {
+ var results []string
+ results = append(results, fmt.Sprintf("## %ss", kind))
+
+ // Type assertion for unstructured list
+ listMap, ok := list.(map[string]interface{})
+ if !ok {
+ // Try unstructured.Unstructured
+ type unstructuredLike interface {
+ UnstructuredContent() map[string]interface{}
+ }
+ if u, ok := list.(unstructuredLike); ok {
+ listMap = u.UnstructuredContent()
+ }
+ }
+
+ if listMap == nil {
+ return fmt.Sprintf("## %ss\nNo data available\n", kind)
+ }
+
+ items, ok := listMap["items"].([]interface{})
+ if !ok || len(items) == 0 {
+ return fmt.Sprintf("## %ss\nNo %ss found\n", kind, kind)
+ }
+
+ results = append(results, "| Name | Namespace | Status | Capabilities | Message |")
+ results = append(results, "|------|-----------|--------|--------------|---------|")
+
+ for _, item := range items {
+ itemMap, ok := item.(map[string]interface{})
+ if !ok {
+ continue
+ }
+
+ name := "unknown"
+ namespace := "-"
+ status := "Unknown"
+ capabilities := "-"
+ message := "-"
+
+ if metadata, ok := itemMap["metadata"].(map[string]interface{}); ok {
+ if n, ok := metadata["name"].(string); ok {
+ name = n
+ }
+ if ns, ok := metadata["namespace"].(string); ok {
+ namespace = ns
+ }
+ }
+
+ if statusMap, ok := itemMap["status"].(map[string]interface{}); ok {
+ if cap, ok := statusMap["capabilities"].(string); ok {
+ capabilities = cap
+ }
+ if conditions, ok := statusMap["conditions"].([]interface{}); ok {
+ for _, cond := range conditions {
+ if condMap, ok := cond.(map[string]interface{}); ok {
+ if condType, ok := condMap["type"].(string); ok && condType == "Ready" {
+ if condStatus, ok := condMap["status"].(string); ok {
+ if condStatus == "True" {
+ status = "✅ Valid"
+ } else {
+ status = "❌ Invalid"
+ }
+ }
+ if msg, ok := condMap["message"].(string); ok {
+ message = msg
+ if len(message) > 50 {
+ message = message[:50] + "..."
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ results = append(results, fmt.Sprintf("| %s | %s | %s | %s | %s |", name, namespace, status, capabilities, message))
+ }
+
+ return strings.Join(results, "\n") + "\n"
+}
diff --git a/pkg/toolsets/externalsecrets/toolset.go b/pkg/toolsets/externalsecrets/toolset.go
new file mode 100644
index 000000000..f30828403
--- /dev/null
+++ b/pkg/toolsets/externalsecrets/toolset.go
@@ -0,0 +1,43 @@
+package externalsecrets
+
+import (
+ "slices"
+
+ "github.com/containers/kubernetes-mcp-server/pkg/api"
+ internalk8s "github.com/containers/kubernetes-mcp-server/pkg/kubernetes"
+ "github.com/containers/kubernetes-mcp-server/pkg/toolsets"
+)
+
+// Toolset provides tools for managing the External Secrets Operator for Red Hat OpenShift.
+// This includes operator installation, configuration, SecretStore/ExternalSecret management,
+// debugging, and status monitoring.
+//
+// References:
+// - https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html/security_and_compliance/external-secrets-operator-for-red-hat-openshift
+// - https://external-secrets.io/latest/
+// - https://github.com/openshift/external-secrets-operator
+// - https://github.com/openshift/external-secrets
+type Toolset struct{}
+
+var _ api.Toolset = (*Toolset)(nil)
+
+func (t *Toolset) GetName() string {
+ return "external-secrets"
+}
+
+func (t *Toolset) GetDescription() string {
+ return "Tools for managing External Secrets Operator for Red Hat OpenShift - operator installation, configuration, SecretStore/ExternalSecret management, and debugging"
+}
+
+func (t *Toolset) GetTools(_ internalk8s.Openshift) []api.ServerTool {
+ return slices.Concat(
+ initOperatorTools(),
+ initStoreTools(),
+ initSecretTools(),
+ initStatusTools(),
+ )
+}
+
+func init() {
+ toolsets.Register(&Toolset{})
+}