From 95675c2c1f51a4b5bce5537a327f001149b81a6d Mon Sep 17 00:00:00 2001 From: nick-gorse Date: Fri, 29 May 2026 17:58:47 +0100 Subject: [PATCH] update crds --- crdSchemas/embeddingserver_v1alpha1.json | 373 +++ crdSchemas/embeddingserver_v1beta1.json | 373 +++ .../embeddingserver-stable-v1alpha1.json | 373 +++ .../embeddingserver-stable-v1beta1.json | 373 +++ ...mcpexternalauthconfig-stable-v1alpha1.json | 1147 +++++++ .../mcpexternalauthconfig-stable-v1beta1.json | 1147 +++++++ .../mcpgroup-stable-v1alpha1.json | 149 + .../mcpgroup-stable-v1beta1.json | 149 + .../mcpoidcconfig-stable-v1alpha1.json | 286 ++ .../mcpoidcconfig-stable-v1beta1.json | 286 ++ .../mcpregistry-stable-v1alpha1.json | 196 ++ .../mcpregistry-stable-v1beta1.json | 196 ++ .../mcpremoteproxy-stable-v1alpha1.json | 621 ++++ .../mcpremoteproxy-stable-v1beta1.json | 621 ++++ .../mcpserver-stable-v1alpha1.json | 946 ++++++ .../mcpserver-stable-v1beta1.json | 946 ++++++ .../mcpserverentry-stable-v1alpha1.json | 242 ++ .../mcpserverentry-stable-v1beta1.json | 242 ++ .../mcptelemetryconfig-stable-v1alpha1.json | 295 ++ .../mcptelemetryconfig-stable-v1beta1.json | 295 ++ .../mcptoolconfig-stable-v1alpha1.json | 192 ++ .../mcptoolconfig-stable-v1beta1.json | 192 ++ .../mcpwebhookconfig-stable-v1alpha1.json | 393 +++ .../podlogs-stable-v1alpha2.json | 183 ++ ...mpositetooldefinition-stable-v1alpha1.json | 352 +++ ...ompositetooldefinition-stable-v1beta1.json | 352 +++ .../virtualmcpserver-stable-v1alpha1.json | 2694 +++++++++++++++++ .../virtualmcpserver-stable-v1beta1.json | 2694 +++++++++++++++++ .../mcpexternalauthconfig_v1alpha1.json | 1147 +++++++ crdSchemas/mcpexternalauthconfig_v1beta1.json | 1147 +++++++ crdSchemas/mcpgroup_v1alpha1.json | 149 + crdSchemas/mcpgroup_v1beta1.json | 149 + crdSchemas/mcpoidcconfig_v1alpha1.json | 286 ++ crdSchemas/mcpoidcconfig_v1beta1.json | 286 ++ crdSchemas/mcpregistry_v1alpha1.json | 196 ++ crdSchemas/mcpregistry_v1beta1.json | 196 ++ crdSchemas/mcpremoteproxy_v1alpha1.json | 621 ++++ crdSchemas/mcpremoteproxy_v1beta1.json | 621 ++++ crdSchemas/mcpserver_v1alpha1.json | 946 ++++++ crdSchemas/mcpserver_v1beta1.json | 946 ++++++ crdSchemas/mcpserverentry_v1alpha1.json | 242 ++ crdSchemas/mcpserverentry_v1beta1.json | 242 ++ crdSchemas/mcptelemetryconfig_v1alpha1.json | 295 ++ crdSchemas/mcptelemetryconfig_v1beta1.json | 295 ++ crdSchemas/mcptoolconfig_v1alpha1.json | 192 ++ crdSchemas/mcptoolconfig_v1beta1.json | 192 ++ crdSchemas/mcpwebhookconfig_v1alpha1.json | 393 +++ crdSchemas/podlogs_v1alpha2.json | 183 ++ ...almcpcompositetooldefinition_v1alpha1.json | 352 +++ ...ualmcpcompositetooldefinition_v1beta1.json | 352 +++ crdSchemas/virtualmcpserver_v1alpha1.json | 2694 +++++++++++++++++ crdSchemas/virtualmcpserver_v1beta1.json | 2694 +++++++++++++++++ 52 files changed, 31124 insertions(+) create mode 100644 crdSchemas/embeddingserver_v1alpha1.json create mode 100644 crdSchemas/embeddingserver_v1beta1.json create mode 100644 crdSchemas/master-standalone/embeddingserver-stable-v1alpha1.json create mode 100644 crdSchemas/master-standalone/embeddingserver-stable-v1beta1.json create mode 100644 crdSchemas/master-standalone/mcpexternalauthconfig-stable-v1alpha1.json create mode 100644 crdSchemas/master-standalone/mcpexternalauthconfig-stable-v1beta1.json create mode 100644 crdSchemas/master-standalone/mcpgroup-stable-v1alpha1.json create mode 100644 crdSchemas/master-standalone/mcpgroup-stable-v1beta1.json create mode 100644 crdSchemas/master-standalone/mcpoidcconfig-stable-v1alpha1.json create mode 100644 crdSchemas/master-standalone/mcpoidcconfig-stable-v1beta1.json create mode 100644 crdSchemas/master-standalone/mcpregistry-stable-v1alpha1.json create mode 100644 crdSchemas/master-standalone/mcpregistry-stable-v1beta1.json create mode 100644 crdSchemas/master-standalone/mcpremoteproxy-stable-v1alpha1.json create mode 100644 crdSchemas/master-standalone/mcpremoteproxy-stable-v1beta1.json create mode 100644 crdSchemas/master-standalone/mcpserver-stable-v1alpha1.json create mode 100644 crdSchemas/master-standalone/mcpserver-stable-v1beta1.json create mode 100644 crdSchemas/master-standalone/mcpserverentry-stable-v1alpha1.json create mode 100644 crdSchemas/master-standalone/mcpserverentry-stable-v1beta1.json create mode 100644 crdSchemas/master-standalone/mcptelemetryconfig-stable-v1alpha1.json create mode 100644 crdSchemas/master-standalone/mcptelemetryconfig-stable-v1beta1.json create mode 100644 crdSchemas/master-standalone/mcptoolconfig-stable-v1alpha1.json create mode 100644 crdSchemas/master-standalone/mcptoolconfig-stable-v1beta1.json create mode 100644 crdSchemas/master-standalone/mcpwebhookconfig-stable-v1alpha1.json create mode 100644 crdSchemas/master-standalone/podlogs-stable-v1alpha2.json create mode 100644 crdSchemas/master-standalone/virtualmcpcompositetooldefinition-stable-v1alpha1.json create mode 100644 crdSchemas/master-standalone/virtualmcpcompositetooldefinition-stable-v1beta1.json create mode 100644 crdSchemas/master-standalone/virtualmcpserver-stable-v1alpha1.json create mode 100644 crdSchemas/master-standalone/virtualmcpserver-stable-v1beta1.json create mode 100644 crdSchemas/mcpexternalauthconfig_v1alpha1.json create mode 100644 crdSchemas/mcpexternalauthconfig_v1beta1.json create mode 100644 crdSchemas/mcpgroup_v1alpha1.json create mode 100644 crdSchemas/mcpgroup_v1beta1.json create mode 100644 crdSchemas/mcpoidcconfig_v1alpha1.json create mode 100644 crdSchemas/mcpoidcconfig_v1beta1.json create mode 100644 crdSchemas/mcpregistry_v1alpha1.json create mode 100644 crdSchemas/mcpregistry_v1beta1.json create mode 100644 crdSchemas/mcpremoteproxy_v1alpha1.json create mode 100644 crdSchemas/mcpremoteproxy_v1beta1.json create mode 100644 crdSchemas/mcpserver_v1alpha1.json create mode 100644 crdSchemas/mcpserver_v1beta1.json create mode 100644 crdSchemas/mcpserverentry_v1alpha1.json create mode 100644 crdSchemas/mcpserverentry_v1beta1.json create mode 100644 crdSchemas/mcptelemetryconfig_v1alpha1.json create mode 100644 crdSchemas/mcptelemetryconfig_v1beta1.json create mode 100644 crdSchemas/mcptoolconfig_v1alpha1.json create mode 100644 crdSchemas/mcptoolconfig_v1beta1.json create mode 100644 crdSchemas/mcpwebhookconfig_v1alpha1.json create mode 100644 crdSchemas/podlogs_v1alpha2.json create mode 100644 crdSchemas/virtualmcpcompositetooldefinition_v1alpha1.json create mode 100644 crdSchemas/virtualmcpcompositetooldefinition_v1beta1.json create mode 100644 crdSchemas/virtualmcpserver_v1alpha1.json create mode 100644 crdSchemas/virtualmcpserver_v1beta1.json diff --git a/crdSchemas/embeddingserver_v1alpha1.json b/crdSchemas/embeddingserver_v1alpha1.json new file mode 100644 index 0000000..55f606b --- /dev/null +++ b/crdSchemas/embeddingserver_v1alpha1.json @@ -0,0 +1,373 @@ +{ + "description": "EmbeddingServer is the deprecated v1alpha1 version of the EmbeddingServer resource.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "EmbeddingServerSpec defines the desired state of EmbeddingServer", + "properties": { + "args": { + "description": "Args are additional arguments to pass to the embedding inference server", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "env": { + "description": "Env are environment variables to set in the container", + "items": { + "description": "EnvVar represents an environment variable in a container", + "properties": { + "name": { + "description": "Name of the environment variable", + "type": "string" + }, + "value": { + "description": "Value of the environment variable", + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + }, + "hfTokenSecretRef": { + "description": "HFTokenSecretRef is a reference to a Kubernetes Secret containing the huggingface token.\nIf provided, the secret value will be provided to the embedding server for authentication with huggingface.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "image": { + "default": "ghcr.io/huggingface/text-embeddings-inference:cpu-latest", + "description": "Image is the container image for the embedding inference server.\nImages must be from HuggingFace Text Embeddings Inference (https://github.com/huggingface/text-embeddings-inference).", + "type": "string" + }, + "imagePullPolicy": { + "default": "IfNotPresent", + "description": "ImagePullPolicy defines the pull policy for the container image", + "enum": [ + "Always", + "Never", + "IfNotPresent" + ], + "type": "string" + }, + "model": { + "default": "BAAI/bge-small-en-v1.5", + "description": "Model is the HuggingFace embedding model to use (e.g., \"sentence-transformers/all-MiniLM-L6-v2\")", + "type": "string" + }, + "modelCache": { + "description": "ModelCache configures persistent storage for downloaded models\nWhen enabled, models are cached in a PVC and reused across pod restarts", + "properties": { + "accessMode": { + "default": "ReadWriteOnce", + "description": "AccessMode is the access mode for the PVC", + "enum": [ + "ReadWriteOnce", + "ReadWriteMany", + "ReadOnlyMany" + ], + "type": "string" + }, + "enabled": { + "default": true, + "description": "Enabled controls whether model caching is enabled", + "type": "boolean" + }, + "size": { + "default": "10Gi", + "description": "Size is the size of the PVC for model caching (e.g., \"10Gi\")", + "type": "string" + }, + "storageClassName": { + "description": "StorageClassName is the storage class to use for the PVC\nIf not specified, uses the cluster's default storage class", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "podTemplateSpec": { + "description": "PodTemplateSpec allows customizing the pod (node selection, tolerations, etc.)\nThis field accepts a PodTemplateSpec object as JSON/YAML.\nNote that to modify the specific container the embedding server runs in, you must specify\nthe 'embedding' container name in the PodTemplateSpec.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "port": { + "default": 8080, + "description": "Port is the port to expose the embedding service on", + "format": "int32", + "maximum": 65535, + "minimum": 1, + "type": "integer" + }, + "replicas": { + "default": 1, + "description": "Replicas is the number of embedding server replicas to run", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "resourceOverrides": { + "description": "ResourceOverrides allows overriding annotations and labels for resources created by the operator", + "properties": { + "persistentVolumeClaim": { + "description": "PersistentVolumeClaim defines overrides for the PVC resource", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + }, + "service": { + "description": "Service defines overrides for the Service resource", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + }, + "statefulSet": { + "description": "StatefulSet defines overrides for the StatefulSet resource", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + }, + "podTemplateMetadataOverrides": { + "description": "PodTemplateMetadataOverrides defines metadata overrides for the pod template", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "resources": { + "description": "Resources defines compute resources for the embedding server", + "properties": { + "limits": { + "description": "Limits describes the maximum amount of compute resources allowed", + "properties": { + "cpu": { + "description": "CPU is the CPU limit in cores (e.g., \"500m\" for 0.5 cores)", + "type": "string" + }, + "memory": { + "description": "Memory is the memory limit in bytes (e.g., \"64Mi\" for 64 megabytes)", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "requests": { + "description": "Requests describes the minimum amount of compute resources required", + "properties": { + "cpu": { + "description": "CPU is the CPU limit in cores (e.g., \"500m\" for 0.5 cores)", + "type": "string" + }, + "memory": { + "description": "Memory is the memory limit in bytes (e.g., \"64Mi\" for 64 megabytes)", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "status": { + "description": "EmbeddingServerStatus defines the observed state of EmbeddingServer", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations of the EmbeddingServer's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "message": { + "description": "Message provides additional information about the current phase", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration reflects the generation most recently observed by the controller", + "format": "int64", + "type": "integer" + }, + "phase": { + "description": "Phase is the current phase of the EmbeddingServer", + "enum": [ + "Pending", + "Downloading", + "Ready", + "Failed", + "Terminating" + ], + "type": "string" + }, + "readyReplicas": { + "description": "ReadyReplicas is the number of ready replicas", + "format": "int32", + "type": "integer" + }, + "url": { + "description": "URL is the URL where the embedding service can be accessed", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/embeddingserver_v1beta1.json b/crdSchemas/embeddingserver_v1beta1.json new file mode 100644 index 0000000..94190c8 --- /dev/null +++ b/crdSchemas/embeddingserver_v1beta1.json @@ -0,0 +1,373 @@ +{ + "description": "EmbeddingServer is the Schema for the embeddingservers API", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "EmbeddingServerSpec defines the desired state of EmbeddingServer", + "properties": { + "args": { + "description": "Args are additional arguments to pass to the embedding inference server", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "env": { + "description": "Env are environment variables to set in the container", + "items": { + "description": "EnvVar represents an environment variable in a container", + "properties": { + "name": { + "description": "Name of the environment variable", + "type": "string" + }, + "value": { + "description": "Value of the environment variable", + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + }, + "hfTokenSecretRef": { + "description": "HFTokenSecretRef is a reference to a Kubernetes Secret containing the huggingface token.\nIf provided, the secret value will be provided to the embedding server for authentication with huggingface.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "image": { + "default": "ghcr.io/huggingface/text-embeddings-inference:cpu-latest", + "description": "Image is the container image for the embedding inference server.\nImages must be from HuggingFace Text Embeddings Inference (https://github.com/huggingface/text-embeddings-inference).", + "type": "string" + }, + "imagePullPolicy": { + "default": "IfNotPresent", + "description": "ImagePullPolicy defines the pull policy for the container image", + "enum": [ + "Always", + "Never", + "IfNotPresent" + ], + "type": "string" + }, + "model": { + "default": "BAAI/bge-small-en-v1.5", + "description": "Model is the HuggingFace embedding model to use (e.g., \"sentence-transformers/all-MiniLM-L6-v2\")", + "type": "string" + }, + "modelCache": { + "description": "ModelCache configures persistent storage for downloaded models\nWhen enabled, models are cached in a PVC and reused across pod restarts", + "properties": { + "accessMode": { + "default": "ReadWriteOnce", + "description": "AccessMode is the access mode for the PVC", + "enum": [ + "ReadWriteOnce", + "ReadWriteMany", + "ReadOnlyMany" + ], + "type": "string" + }, + "enabled": { + "default": true, + "description": "Enabled controls whether model caching is enabled", + "type": "boolean" + }, + "size": { + "default": "10Gi", + "description": "Size is the size of the PVC for model caching (e.g., \"10Gi\")", + "type": "string" + }, + "storageClassName": { + "description": "StorageClassName is the storage class to use for the PVC\nIf not specified, uses the cluster's default storage class", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "podTemplateSpec": { + "description": "PodTemplateSpec allows customizing the pod (node selection, tolerations, etc.)\nThis field accepts a PodTemplateSpec object as JSON/YAML.\nNote that to modify the specific container the embedding server runs in, you must specify\nthe 'embedding' container name in the PodTemplateSpec.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "port": { + "default": 8080, + "description": "Port is the port to expose the embedding service on", + "format": "int32", + "maximum": 65535, + "minimum": 1, + "type": "integer" + }, + "replicas": { + "default": 1, + "description": "Replicas is the number of embedding server replicas to run", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "resourceOverrides": { + "description": "ResourceOverrides allows overriding annotations and labels for resources created by the operator", + "properties": { + "persistentVolumeClaim": { + "description": "PersistentVolumeClaim defines overrides for the PVC resource", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + }, + "service": { + "description": "Service defines overrides for the Service resource", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + }, + "statefulSet": { + "description": "StatefulSet defines overrides for the StatefulSet resource", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + }, + "podTemplateMetadataOverrides": { + "description": "PodTemplateMetadataOverrides defines metadata overrides for the pod template", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "resources": { + "description": "Resources defines compute resources for the embedding server", + "properties": { + "limits": { + "description": "Limits describes the maximum amount of compute resources allowed", + "properties": { + "cpu": { + "description": "CPU is the CPU limit in cores (e.g., \"500m\" for 0.5 cores)", + "type": "string" + }, + "memory": { + "description": "Memory is the memory limit in bytes (e.g., \"64Mi\" for 64 megabytes)", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "requests": { + "description": "Requests describes the minimum amount of compute resources required", + "properties": { + "cpu": { + "description": "CPU is the CPU limit in cores (e.g., \"500m\" for 0.5 cores)", + "type": "string" + }, + "memory": { + "description": "Memory is the memory limit in bytes (e.g., \"64Mi\" for 64 megabytes)", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "status": { + "description": "EmbeddingServerStatus defines the observed state of EmbeddingServer", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations of the EmbeddingServer's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "message": { + "description": "Message provides additional information about the current phase", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration reflects the generation most recently observed by the controller", + "format": "int64", + "type": "integer" + }, + "phase": { + "description": "Phase is the current phase of the EmbeddingServer", + "enum": [ + "Pending", + "Downloading", + "Ready", + "Failed", + "Terminating" + ], + "type": "string" + }, + "readyReplicas": { + "description": "ReadyReplicas is the number of ready replicas", + "format": "int32", + "type": "integer" + }, + "url": { + "description": "URL is the URL where the embedding service can be accessed", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/master-standalone/embeddingserver-stable-v1alpha1.json b/crdSchemas/master-standalone/embeddingserver-stable-v1alpha1.json new file mode 100644 index 0000000..55f606b --- /dev/null +++ b/crdSchemas/master-standalone/embeddingserver-stable-v1alpha1.json @@ -0,0 +1,373 @@ +{ + "description": "EmbeddingServer is the deprecated v1alpha1 version of the EmbeddingServer resource.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "EmbeddingServerSpec defines the desired state of EmbeddingServer", + "properties": { + "args": { + "description": "Args are additional arguments to pass to the embedding inference server", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "env": { + "description": "Env are environment variables to set in the container", + "items": { + "description": "EnvVar represents an environment variable in a container", + "properties": { + "name": { + "description": "Name of the environment variable", + "type": "string" + }, + "value": { + "description": "Value of the environment variable", + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + }, + "hfTokenSecretRef": { + "description": "HFTokenSecretRef is a reference to a Kubernetes Secret containing the huggingface token.\nIf provided, the secret value will be provided to the embedding server for authentication with huggingface.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "image": { + "default": "ghcr.io/huggingface/text-embeddings-inference:cpu-latest", + "description": "Image is the container image for the embedding inference server.\nImages must be from HuggingFace Text Embeddings Inference (https://github.com/huggingface/text-embeddings-inference).", + "type": "string" + }, + "imagePullPolicy": { + "default": "IfNotPresent", + "description": "ImagePullPolicy defines the pull policy for the container image", + "enum": [ + "Always", + "Never", + "IfNotPresent" + ], + "type": "string" + }, + "model": { + "default": "BAAI/bge-small-en-v1.5", + "description": "Model is the HuggingFace embedding model to use (e.g., \"sentence-transformers/all-MiniLM-L6-v2\")", + "type": "string" + }, + "modelCache": { + "description": "ModelCache configures persistent storage for downloaded models\nWhen enabled, models are cached in a PVC and reused across pod restarts", + "properties": { + "accessMode": { + "default": "ReadWriteOnce", + "description": "AccessMode is the access mode for the PVC", + "enum": [ + "ReadWriteOnce", + "ReadWriteMany", + "ReadOnlyMany" + ], + "type": "string" + }, + "enabled": { + "default": true, + "description": "Enabled controls whether model caching is enabled", + "type": "boolean" + }, + "size": { + "default": "10Gi", + "description": "Size is the size of the PVC for model caching (e.g., \"10Gi\")", + "type": "string" + }, + "storageClassName": { + "description": "StorageClassName is the storage class to use for the PVC\nIf not specified, uses the cluster's default storage class", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "podTemplateSpec": { + "description": "PodTemplateSpec allows customizing the pod (node selection, tolerations, etc.)\nThis field accepts a PodTemplateSpec object as JSON/YAML.\nNote that to modify the specific container the embedding server runs in, you must specify\nthe 'embedding' container name in the PodTemplateSpec.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "port": { + "default": 8080, + "description": "Port is the port to expose the embedding service on", + "format": "int32", + "maximum": 65535, + "minimum": 1, + "type": "integer" + }, + "replicas": { + "default": 1, + "description": "Replicas is the number of embedding server replicas to run", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "resourceOverrides": { + "description": "ResourceOverrides allows overriding annotations and labels for resources created by the operator", + "properties": { + "persistentVolumeClaim": { + "description": "PersistentVolumeClaim defines overrides for the PVC resource", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + }, + "service": { + "description": "Service defines overrides for the Service resource", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + }, + "statefulSet": { + "description": "StatefulSet defines overrides for the StatefulSet resource", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + }, + "podTemplateMetadataOverrides": { + "description": "PodTemplateMetadataOverrides defines metadata overrides for the pod template", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "resources": { + "description": "Resources defines compute resources for the embedding server", + "properties": { + "limits": { + "description": "Limits describes the maximum amount of compute resources allowed", + "properties": { + "cpu": { + "description": "CPU is the CPU limit in cores (e.g., \"500m\" for 0.5 cores)", + "type": "string" + }, + "memory": { + "description": "Memory is the memory limit in bytes (e.g., \"64Mi\" for 64 megabytes)", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "requests": { + "description": "Requests describes the minimum amount of compute resources required", + "properties": { + "cpu": { + "description": "CPU is the CPU limit in cores (e.g., \"500m\" for 0.5 cores)", + "type": "string" + }, + "memory": { + "description": "Memory is the memory limit in bytes (e.g., \"64Mi\" for 64 megabytes)", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "status": { + "description": "EmbeddingServerStatus defines the observed state of EmbeddingServer", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations of the EmbeddingServer's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "message": { + "description": "Message provides additional information about the current phase", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration reflects the generation most recently observed by the controller", + "format": "int64", + "type": "integer" + }, + "phase": { + "description": "Phase is the current phase of the EmbeddingServer", + "enum": [ + "Pending", + "Downloading", + "Ready", + "Failed", + "Terminating" + ], + "type": "string" + }, + "readyReplicas": { + "description": "ReadyReplicas is the number of ready replicas", + "format": "int32", + "type": "integer" + }, + "url": { + "description": "URL is the URL where the embedding service can be accessed", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/master-standalone/embeddingserver-stable-v1beta1.json b/crdSchemas/master-standalone/embeddingserver-stable-v1beta1.json new file mode 100644 index 0000000..94190c8 --- /dev/null +++ b/crdSchemas/master-standalone/embeddingserver-stable-v1beta1.json @@ -0,0 +1,373 @@ +{ + "description": "EmbeddingServer is the Schema for the embeddingservers API", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "EmbeddingServerSpec defines the desired state of EmbeddingServer", + "properties": { + "args": { + "description": "Args are additional arguments to pass to the embedding inference server", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "env": { + "description": "Env are environment variables to set in the container", + "items": { + "description": "EnvVar represents an environment variable in a container", + "properties": { + "name": { + "description": "Name of the environment variable", + "type": "string" + }, + "value": { + "description": "Value of the environment variable", + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + }, + "hfTokenSecretRef": { + "description": "HFTokenSecretRef is a reference to a Kubernetes Secret containing the huggingface token.\nIf provided, the secret value will be provided to the embedding server for authentication with huggingface.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "image": { + "default": "ghcr.io/huggingface/text-embeddings-inference:cpu-latest", + "description": "Image is the container image for the embedding inference server.\nImages must be from HuggingFace Text Embeddings Inference (https://github.com/huggingface/text-embeddings-inference).", + "type": "string" + }, + "imagePullPolicy": { + "default": "IfNotPresent", + "description": "ImagePullPolicy defines the pull policy for the container image", + "enum": [ + "Always", + "Never", + "IfNotPresent" + ], + "type": "string" + }, + "model": { + "default": "BAAI/bge-small-en-v1.5", + "description": "Model is the HuggingFace embedding model to use (e.g., \"sentence-transformers/all-MiniLM-L6-v2\")", + "type": "string" + }, + "modelCache": { + "description": "ModelCache configures persistent storage for downloaded models\nWhen enabled, models are cached in a PVC and reused across pod restarts", + "properties": { + "accessMode": { + "default": "ReadWriteOnce", + "description": "AccessMode is the access mode for the PVC", + "enum": [ + "ReadWriteOnce", + "ReadWriteMany", + "ReadOnlyMany" + ], + "type": "string" + }, + "enabled": { + "default": true, + "description": "Enabled controls whether model caching is enabled", + "type": "boolean" + }, + "size": { + "default": "10Gi", + "description": "Size is the size of the PVC for model caching (e.g., \"10Gi\")", + "type": "string" + }, + "storageClassName": { + "description": "StorageClassName is the storage class to use for the PVC\nIf not specified, uses the cluster's default storage class", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "podTemplateSpec": { + "description": "PodTemplateSpec allows customizing the pod (node selection, tolerations, etc.)\nThis field accepts a PodTemplateSpec object as JSON/YAML.\nNote that to modify the specific container the embedding server runs in, you must specify\nthe 'embedding' container name in the PodTemplateSpec.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "port": { + "default": 8080, + "description": "Port is the port to expose the embedding service on", + "format": "int32", + "maximum": 65535, + "minimum": 1, + "type": "integer" + }, + "replicas": { + "default": 1, + "description": "Replicas is the number of embedding server replicas to run", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "resourceOverrides": { + "description": "ResourceOverrides allows overriding annotations and labels for resources created by the operator", + "properties": { + "persistentVolumeClaim": { + "description": "PersistentVolumeClaim defines overrides for the PVC resource", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + }, + "service": { + "description": "Service defines overrides for the Service resource", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + }, + "statefulSet": { + "description": "StatefulSet defines overrides for the StatefulSet resource", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + }, + "podTemplateMetadataOverrides": { + "description": "PodTemplateMetadataOverrides defines metadata overrides for the pod template", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "resources": { + "description": "Resources defines compute resources for the embedding server", + "properties": { + "limits": { + "description": "Limits describes the maximum amount of compute resources allowed", + "properties": { + "cpu": { + "description": "CPU is the CPU limit in cores (e.g., \"500m\" for 0.5 cores)", + "type": "string" + }, + "memory": { + "description": "Memory is the memory limit in bytes (e.g., \"64Mi\" for 64 megabytes)", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "requests": { + "description": "Requests describes the minimum amount of compute resources required", + "properties": { + "cpu": { + "description": "CPU is the CPU limit in cores (e.g., \"500m\" for 0.5 cores)", + "type": "string" + }, + "memory": { + "description": "Memory is the memory limit in bytes (e.g., \"64Mi\" for 64 megabytes)", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "status": { + "description": "EmbeddingServerStatus defines the observed state of EmbeddingServer", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations of the EmbeddingServer's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "message": { + "description": "Message provides additional information about the current phase", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration reflects the generation most recently observed by the controller", + "format": "int64", + "type": "integer" + }, + "phase": { + "description": "Phase is the current phase of the EmbeddingServer", + "enum": [ + "Pending", + "Downloading", + "Ready", + "Failed", + "Terminating" + ], + "type": "string" + }, + "readyReplicas": { + "description": "ReadyReplicas is the number of ready replicas", + "format": "int32", + "type": "integer" + }, + "url": { + "description": "URL is the URL where the embedding service can be accessed", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/master-standalone/mcpexternalauthconfig-stable-v1alpha1.json b/crdSchemas/master-standalone/mcpexternalauthconfig-stable-v1alpha1.json new file mode 100644 index 0000000..308902d --- /dev/null +++ b/crdSchemas/master-standalone/mcpexternalauthconfig-stable-v1alpha1.json @@ -0,0 +1,1147 @@ +{ + "description": "MCPExternalAuthConfig is the deprecated v1alpha1 version of the MCPExternalAuthConfig resource.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPExternalAuthConfigSpec defines the desired state of MCPExternalAuthConfig.\nMCPExternalAuthConfig resources are namespace-scoped and can only be referenced by\nMCPServer resources in the same namespace.", + "properties": { + "awsSts": { + "description": "AWSSts configures AWS STS authentication with SigV4 request signing\nOnly used when Type is \"awsSts\"", + "properties": { + "fallbackRoleArn": { + "description": "FallbackRoleArn is the IAM role ARN to assume when no role mappings match\nUsed as the default role when RoleMappings is empty or no mapping matches\nAt least one of FallbackRoleArn or RoleMappings must be configured (enforced by webhook)", + "pattern": "^arn:(aws|aws-cn|aws-us-gov):iam::\\d{12}:role/[\\w+=,.@\\-_/]+$", + "type": "string" + }, + "region": { + "description": "Region is the AWS region for the STS endpoint and service (e.g., \"us-east-1\", \"eu-west-1\")", + "minLength": 1, + "pattern": "^[a-z]{2}(-[a-z]+)+-\\d+$", + "type": "string" + }, + "roleClaim": { + "default": "groups", + "description": "RoleClaim is the JWT claim to use for role mapping evaluation\nDefaults to \"groups\" to match common OIDC group claims", + "type": "string" + }, + "roleMappings": { + "description": "RoleMappings defines claim-based role selection rules\nAllows mapping JWT claims (e.g., groups, roles) to specific IAM roles\nLower priority values are evaluated first (higher priority)", + "items": { + "description": "RoleMapping defines a rule for mapping JWT claims to IAM roles.\nMappings are evaluated in priority order (lower number = higher priority), and the first\nmatching rule determines which IAM role to assume.\nExactly one of Claim or Matcher must be specified.", + "properties": { + "claim": { + "description": "Claim is a simple claim value to match against\nThe claim type is specified by AWSStsConfig.RoleClaim\nFor example, if RoleClaim is \"groups\", this would be a group name\nInternally compiled to a CEL expression: \"\" in claims[\"\"]\nMutually exclusive with Matcher", + "minLength": 1, + "type": "string" + }, + "matcher": { + "description": "Matcher is a CEL expression for complex matching against JWT claims\nThe expression has access to a \"claims\" variable containing all JWT claims as map[string]any\nExamples:\n - \"admins\" in claims[\"groups\"]\n - claims[\"sub\"] == \"user123\" && !(\"act\" in claims)\nMutually exclusive with Claim", + "minLength": 1, + "type": "string" + }, + "priority": { + "description": "Priority determines evaluation order (lower values = higher priority)\nAllows fine-grained control over role selection precedence\nWhen omitted, this mapping has the lowest possible priority and\nconfiguration order acts as tie-breaker via stable sort", + "format": "int32", + "minimum": 0, + "type": "integer" + }, + "roleArn": { + "description": "RoleArn is the IAM role ARN to assume when this mapping matches", + "pattern": "^arn:(aws|aws-cn|aws-us-gov):iam::\\d{12}:role/[\\w+=,.@\\-_/]+$", + "type": "string" + } + }, + "required": [ + "roleArn" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "service": { + "default": "aws-mcp", + "description": "Service is the AWS service name for SigV4 signing\nDefaults to \"aws-mcp\" for AWS MCP Server endpoints", + "type": "string" + }, + "sessionDuration": { + "default": 3600, + "description": "SessionDuration is the duration in seconds for the STS session\nMust be between 900 (15 minutes) and 43200 (12 hours)\nDefaults to 3600 (1 hour) if not specified", + "format": "int32", + "maximum": 43200, + "minimum": 900, + "type": "integer" + }, + "sessionNameClaim": { + "default": "sub", + "description": "SessionNameClaim is the JWT claim to use for role session name\nDefaults to \"sub\" to use the subject claim", + "type": "string" + }, + "subjectProviderName": { + "description": "SubjectProviderName is the name of the upstream provider whose access token\nis used as the web identity token for STS AssumeRoleWithWebIdentity.\nThis field is used exclusively by VirtualMCPServer, where there is no\nupstream swap middleware to replace the bearer token before the strategy runs.\nWhen left empty and an embedded authorization server is configured on the\nVirtualMCPServer, the controller automatically populates this field with\nthe first configured upstream provider name. Set it explicitly to override\nthat default or to select a specific provider when multiple upstreams are\nconfigured.\nWhen no embedded auth server is present, the bearer token from the incoming\nrequest's Authorization header is used instead.", + "type": "string" + } + }, + "required": [ + "region" + ], + "type": "object", + "additionalProperties": false + }, + "bearerToken": { + "description": "BearerToken configures bearer token authentication\nOnly used when Type is \"bearerToken\"", + "properties": { + "tokenSecretRef": { + "description": "TokenSecretRef references a Kubernetes Secret containing the bearer token", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "tokenSecretRef" + ], + "type": "object", + "additionalProperties": false + }, + "embeddedAuthServer": { + "description": "EmbeddedAuthServer configures an embedded OAuth2/OIDC authorization server\nOnly used when Type is \"embeddedAuthServer\"", + "properties": { + "authorizationEndpointBaseUrl": { + "description": "AuthorizationEndpointBaseURL overrides the base URL used for the authorization_endpoint\nin the OAuth discovery document. When set, the discovery document will advertise\n`{authorizationEndpointBaseUrl}/oauth/authorize` instead of `{issuer}/oauth/authorize`.\nAll other endpoints (token, registration, JWKS) remain derived from the issuer.\nThis is useful when the browser-facing authorization endpoint needs to be on a\ndifferent host than the issuer used for backend-to-backend calls.\nMust be a valid HTTPS URL (or HTTP for localhost) without query, fragment, or trailing slash.", + "pattern": "^https?://[^\\s?#]+[^/\\s?#]$", + "type": "string" + }, + "baselineClientScopes": { + "description": "BaselineClientScopes is a baseline set of OAuth 2.0 scopes guaranteed to be\nincluded in every client registration. The embedded auth server unions these\nscopes into the registered set returned by RFC 7591 Dynamic Client\nRegistration, so a client that narrows the `scope` field at /oauth/register\ncan still request the baseline scopes at /oauth/authorize. All values must\nbe present in the upstream-derived scopesSupported set; the auth server\nfails to start if any value is missing.\n\nSecurity: every client registered via /oauth/register will gain the\nability to request these scopes at /oauth/authorize, regardless of what\nthe client itself requested. Keep the baseline narrow (typically\n\"openid\" and \"offline_access\"). Adding a privileged scope here \u2014 e.g.\n\"admin:read\" \u2014 would grant it to every DCR-registered client, including\npublic clients like Claude Code, Cursor, and VS Code.", + "items": { + "minLength": 1, + "pattern": "^[\\x21\\x23-\\x5B\\x5D-\\x7E]+$", + "type": "string" + }, + "maxItems": 10, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "hmacSecretRefs": { + "description": "HMACSecretRefs references Kubernetes Secrets containing symmetric secrets for signing\nauthorization codes and refresh tokens (opaque tokens).\nCurrent secret must be at least 32 bytes and cryptographically random.\nSupports secret rotation via multiple entries (first is current, rest are for verification).\nIf not specified, an ephemeral secret will be auto-generated (development only -\nauth codes and refresh tokens will be invalid after restart).", + "items": { + "description": "SecretKeyRef is a reference to a key within a Secret", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "issuer": { + "description": "Issuer is the issuer identifier for this authorization server.\nThis will be included in the \"iss\" claim of issued tokens.\nMust be a valid HTTPS URL (or HTTP for localhost) without query, fragment, or trailing slash (per RFC 8414).", + "pattern": "^https?://[^\\s?#]+[^/\\s?#]$", + "type": "string" + }, + "primaryUpstreamProvider": { + "description": "PrimaryUpstreamProvider names the upstream IDP whose access token Cedar\nshould read claims from when authorising a request. Must match the name\nof one of the entries in UpstreamProviders. When empty, the controller\nauto-selects the first entry of UpstreamProviders.\n\nOnly meaningful on VirtualMCPServer, where multiple upstream providers\ncan be configured and Cedar needs to pick which token's claims to\nevaluate. The VirtualMCPServer controller validates this field against\nUpstreamProviders at admission and rejects unresolvable values.\n\nOn MCPServer and MCPRemoteProxy this field is structurally present (the\nEmbeddedAuthServerConfig struct is shared) but has no runtime effect:\nthose CRDs are restricted to a single upstream so there is no choice to\nmake. Setting it on those CRDs is silently ignored.", + "maxLength": 63, + "minLength": 1, + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "type": "string" + }, + "signingKeySecretRefs": { + "description": "SigningKeySecretRefs references Kubernetes Secrets containing signing keys for JWT operations.\nSupports key rotation by allowing multiple keys (oldest keys are used for verification only).\nIf not specified, an ephemeral signing key will be auto-generated (development only -\nJWTs will be invalid after restart).", + "items": { + "description": "SecretKeyRef is a reference to a key within a Secret", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "maxItems": 5, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "storage": { + "description": "Storage configures the storage backend for the embedded auth server.\nIf not specified, defaults to in-memory storage.", + "properties": { + "redis": { + "description": "Redis configures the Redis storage backend.\nRequired when type is \"redis\".", + "properties": { + "aclUserConfig": { + "description": "ACLUserConfig configures Redis ACL user authentication.", + "properties": { + "passwordSecretRef": { + "description": "PasswordSecretRef references a Secret containing the Redis ACL password.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "usernameSecretRef": { + "description": "UsernameSecretRef references a Secret containing the Redis ACL username.\nWhen omitted, connections use legacy password-only AUTH. Omit for managed\nRedis tiers that do not support ACL users (e.g. GCP Memorystore Basic/Standard\nHA, Azure Cache for Redis). Set for services that support ACL users (e.g. AWS\nElastiCache non-cluster with Redis 6+ RBAC).", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "passwordSecretRef" + ], + "type": "object", + "additionalProperties": false + }, + "addr": { + "description": "Addr is the Redis server address (host:port). Required for standalone and cluster modes.\nUse for managed Redis services that expose a single endpoint (GCP Memorystore basic tier,\nAWS ElastiCache without cluster mode, or cluster-mode services when clusterMode is true).\nMutually exclusive with sentinelConfig.", + "type": "string" + }, + "clusterMode": { + "description": "ClusterMode enables the Redis Cluster protocol. Set to true when addr points to a\nRedis Cluster discovery endpoint (e.g., GCP Memorystore Cluster, AWS ElastiCache\ncluster mode enabled). Requires addr to be set.", + "type": "boolean" + }, + "dialTimeout": { + "default": "5s", + "description": "DialTimeout is the timeout for establishing connections.\nFormat: Go duration string (e.g., \"5s\", \"1m\").", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "readTimeout": { + "default": "3s", + "description": "ReadTimeout is the timeout for socket reads.\nFormat: Go duration string (e.g., \"3s\", \"1m\").", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "sentinelConfig": { + "description": "SentinelConfig holds Redis Sentinel configuration.\nUse for self-managed Redis with Sentinel-based HA. Mutually exclusive with addr.", + "properties": { + "db": { + "default": 0, + "description": "DB is the Redis database number.", + "format": "int32", + "type": "integer" + }, + "masterName": { + "description": "MasterName is the name of the Redis master monitored by Sentinel.", + "type": "string" + }, + "sentinelAddrs": { + "description": "SentinelAddrs is a list of Sentinel host:port addresses.\nMutually exclusive with SentinelService.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "sentinelService": { + "description": "SentinelService enables automatic discovery from a Kubernetes Service.\nMutually exclusive with SentinelAddrs.", + "properties": { + "name": { + "description": "Name of the Sentinel Service.", + "type": "string" + }, + "namespace": { + "description": "Namespace of the Sentinel Service (defaults to same namespace).", + "type": "string" + }, + "port": { + "default": 26379, + "description": "Port of the Sentinel service.", + "format": "int32", + "type": "integer" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "masterName" + ], + "type": "object", + "additionalProperties": false + }, + "sentinelTls": { + "description": "SentinelTLS configures TLS for connections to Sentinel instances.\nOnly applies when sentinelConfig is set. Presence of this field enables TLS.", + "properties": { + "caCertSecretRef": { + "description": "CACertSecretRef references a Secret containing a PEM-encoded CA certificate\nfor verifying the server. When not specified, system root CAs are used.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "insecureSkipVerify": { + "description": "InsecureSkipVerify skips TLS certificate verification.\nUse when connecting to services with self-signed certificates.", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "tls": { + "description": "TLS configures TLS for connections to the Redis/Valkey master.\nPresence of this field enables TLS. Omit to use plaintext.", + "properties": { + "caCertSecretRef": { + "description": "CACertSecretRef references a Secret containing a PEM-encoded CA certificate\nfor verifying the server. When not specified, system root CAs are used.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "insecureSkipVerify": { + "description": "InsecureSkipVerify skips TLS certificate verification.\nUse when connecting to services with self-signed certificates.", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "writeTimeout": { + "default": "3s", + "description": "WriteTimeout is the timeout for socket writes.\nFormat: Go duration string (e.g., \"3s\", \"1m\").", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + } + }, + "required": [ + "aclUserConfig" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "exactly one of addr or sentinelConfig must be set", + "rule": "(has(self.addr) && self.addr.size() > 0) != has(self.sentinelConfig)" + }, + { + "message": "clusterMode requires addr to be set", + "rule": "!(has(self.clusterMode) && self.clusterMode) || (has(self.addr) && self.addr.size() > 0)" + } + ], + "additionalProperties": false + }, + "type": { + "default": "memory", + "description": "Type specifies the storage backend type.\nValid values: \"memory\" (default), \"redis\".", + "enum": [ + "memory", + "redis" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "tokenLifespans": { + "description": "TokenLifespans configures the duration that various tokens are valid.\nIf not specified, defaults are applied (access: 1h, refresh: 7d, authCode: 10m).", + "properties": { + "accessTokenLifespan": { + "description": "AccessTokenLifespan is the duration that access tokens are valid.\nFormat: Go duration string (e.g., \"1h\", \"30m\", \"24h\").\nIf empty, defaults to 1 hour.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "authCodeLifespan": { + "description": "AuthCodeLifespan is the duration that authorization codes are valid.\nFormat: Go duration string (e.g., \"10m\", \"5m\").\nIf empty, defaults to 10 minutes.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "refreshTokenLifespan": { + "description": "RefreshTokenLifespan is the duration that refresh tokens are valid.\nFormat: Go duration string (e.g., \"168h\", \"7d\" as \"168h\").\nIf empty, defaults to 7 days (168h).", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "upstreamProviders": { + "description": "UpstreamProviders configures connections to upstream Identity Providers.\nThe embedded auth server delegates authentication to these providers.\nMCPServer and MCPRemoteProxy support a single upstream; VirtualMCPServer supports multiple.", + "items": { + "description": "UpstreamProviderConfig defines configuration for an upstream Identity Provider.\n\nExactly one of OIDCConfig or OAuth2Config must be set and must match the\ndeclared Type: oidc-typed providers set OIDCConfig, oauth2-typed providers\nset OAuth2Config. The CEL rule below enforces the pairing at admission; the\nmatching Go-level check in validateUpstreamProvider provides defense-in-depth\nfor stored objects.\n\nThe rule is structured as a chain of equality checks ending in an explicit\n`false`, so adding a new UpstreamProviderType value without extending this\nrule fails admission instead of silently demanding the OAuth2 shape. When\nadding a new type, extend both this rule and validateUpstreamProvider.", + "properties": { + "name": { + "description": "Name uniquely identifies this upstream provider.\nUsed for routing decisions and session binding in multi-upstream scenarios.\nMust be lowercase alphanumeric with hyphens (DNS-label-like).", + "maxLength": 63, + "minLength": 1, + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "type": "string" + }, + "oauth2Config": { + "description": "OAuth2Config contains OAuth 2.0-specific configuration.\nRequired when Type is \"oauth2\", must be nil when Type is \"oidc\".", + "properties": { + "additionalAuthorizationParams": { + "additionalProperties": { + "type": "string" + }, + "description": "AdditionalAuthorizationParams are extra query parameters to include in\nauthorization requests sent to the upstream provider.\nThis is useful for providers that require custom parameters, such as\nGoogle's access_type=offline for obtaining refresh tokens.\nFramework-managed parameters (response_type, client_id, redirect_uri,\nscope, state, code_challenge, code_challenge_method, nonce) are not allowed.", + "maxProperties": 16, + "type": "object" + }, + "authorizationEndpoint": { + "description": "AuthorizationEndpoint is the URL for the OAuth authorization endpoint.", + "pattern": "^https?://.*$", + "type": "string" + }, + "clientId": { + "description": "ClientID is the OAuth 2.0 client identifier registered with the upstream IDP.\nMutually exclusive with DCRConfig: when DCRConfig is set, ClientID is obtained\nat runtime via RFC 7591 Dynamic Client Registration and must be left empty.", + "type": "string" + }, + "clientSecretRef": { + "description": "ClientSecretRef references a Kubernetes Secret containing the OAuth 2.0 client secret.\nOptional for public clients using PKCE instead of client secret.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "dcrConfig": { + "description": "DCRConfig enables RFC 7591 Dynamic Client Registration against the upstream\nauthorization server. When set, the client credentials are obtained at\nruntime rather than being pre-provisioned, and ClientID must be left empty.\nMutually exclusive with ClientID.", + "properties": { + "discoveryUrl": { + "description": "DiscoveryURL is the RFC 8414 / OIDC Discovery document URL. The resolver\nissues a single GET against this URL (no well-known-path fallback) and\nreads registration_endpoint, authorization_endpoint, token_endpoint,\ntoken_endpoint_auth_methods_supported, and scopes_supported from the\nresponse.\nMutually exclusive with RegistrationEndpoint.\nHTTPS is required because the registration endpoint resolved from this\ndocument carries the initial access token and the issued client_secret\n(RFC 7591 \u00a73, RFC 8414 \u00a73). MaxLength is a defensive size cap (etcd\nobject budget, regex evaluation cost) and matches the conventional URL\nlength cap.", + "maxLength": 2048, + "pattern": "^https://[^\\s?#]+[^/\\s?#]$", + "type": "string" + }, + "initialAccessTokenRef": { + "description": "InitialAccessTokenRef is an optional reference to a Kubernetes Secret\ncarrying an RFC 7591 \u00a73 initial access token. When set, the resolver\npresents the token value as a Bearer credential on the registration\nrequest. Mirrors the ClientSecretRef pattern.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "registrationEndpoint": { + "description": "RegistrationEndpoint is the RFC 7591 registration endpoint URL used\ndirectly, bypassing discovery. When using this field, the caller is\nexpected to also supply AuthorizationEndpoint, TokenEndpoint, and an\nexplicit Scopes list on the parent OAuth2UpstreamConfig.\nMutually exclusive with DiscoveryURL.\nHTTPS is required because the registration endpoint carries the initial\naccess token and the issued client_secret (RFC 7591 \u00a73, RFC 8414 \u00a73).\nMaxLength is a defensive size cap (etcd object budget, regex evaluation\ncost) and matches the conventional URL length cap.", + "maxLength": 2048, + "pattern": "^https://[^\\s?#]+[^/\\s?#]$", + "type": "string" + }, + "softwareId": { + "description": "SoftwareID is the RFC 7591 \"software_id\" registration metadata value,\nidentifying the client software independent of any particular\nregistration instance. Typically a UUID or short identifier.", + "maxLength": 255, + "type": "string" + }, + "softwareStatement": { + "description": "SoftwareStatement is the RFC 7591 \"software_statement\" JWT asserting\nmetadata about the client software, signed by a party the authorization\nserver trusts.\n\nStored inline on the CR. The JWT is signed but not encrypted, so its\ncontents are visible to anyone with get/list/watch on this resource and\nappear in etcd backups in plaintext. Treat the value as non-confidential\n(signed attestation, not a secret). Operators that rotate software\nstatements like bearer credentials should keep them at the authorization\nserver side and rely on the registration endpoint's initial access\ntoken (see InitialAccessTokenRef) instead of placing them on the CR.\n\nBounded to 16384 characters as a defensive size cap (etcd object\nbudget, regex evaluation cost). Real-world signed statements with\nembedded x5c certificate chains, JWKS keys, or OIDC-Federation\ntrust-framework metadata routinely exceed 4 KB.", + "maxLength": 16384, + "type": "string" + } + }, + "type": "object", + "x-kubernetes-validations": [ + { + "message": "exactly one of discoveryUrl or registrationEndpoint must be set", + "rule": "has(self.discoveryUrl) != has(self.registrationEndpoint)" + } + ], + "additionalProperties": false + }, + "identityFromToken": { + "description": "IdentityFromToken extracts user identity (subject, name, email) directly\nfrom the OAuth2 token-endpoint response body using gjson dot-notation paths.\nWhen set, the embedded auth server skips the userinfo HTTP call entirely\nand resolves identity from the token response. See IdentityFromTokenConfig\nfor trust-model and uniqueness considerations.", + "properties": { + "emailPath": { + "description": "EmailPath is the dot-notation path to the email address field in the token response.\nIf not specified or if the path does not resolve to a string, the email is omitted.\nOmit the field entirely rather than setting it to an empty string.", + "maxLength": 256, + "minLength": 1, + "type": "string" + }, + "namePath": { + "description": "NamePath is the dot-notation path to the display name field in the token response.\nIf not specified or if the path does not resolve to a string, the display name is omitted.\nOmit the field entirely rather than setting it to an empty string.", + "maxLength": 256, + "minLength": 1, + "type": "string" + }, + "subjectPath": { + "description": "SubjectPath is the dot-notation path to the subject (user ID) field in the token response.\nWarning: claims read from the token response are trusted only via TLS, not\ncryptographically verified; prefer OIDC ID tokens when verifiable claims are required.\nExample: \"authed_user.id\" for Slack (top-level token-response field). For providers\nwhose token response embeds the access token as a JWT (e.g. Snowflake), use the\n\"@upstreamjwt\" modifier to decode the payload, e.g. \"access_token|@upstreamjwt|sub\".\nThe \"@upstreamjwt\" modifier performs no signature verification either.", + "maxLength": 256, + "minLength": 1, + "type": "string" + } + }, + "required": [ + "subjectPath" + ], + "type": "object", + "additionalProperties": false + }, + "redirectUri": { + "description": "RedirectURI is the callback URL where the upstream IDP will redirect after authentication.\nWhen not specified, defaults to `{resourceUrl}/oauth/callback` where `resourceUrl` is the\nURL associated with the resource (e.g., MCPServer or vMCP) using this config.", + "type": "string" + }, + "scopes": { + "description": "Scopes are the OAuth scopes to request from the upstream IDP.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "tokenEndpoint": { + "description": "TokenEndpoint is the URL for the OAuth token endpoint.", + "pattern": "^https?://.*$", + "type": "string" + }, + "tokenResponseMapping": { + "description": "TokenResponseMapping configures custom field extraction from non-standard token responses.\nSome OAuth providers (e.g., GovSlack) nest token fields under non-standard paths\ninstead of returning them at the top level. When set, ToolHive performs the token\nexchange HTTP call directly and extracts fields using the configured dot-notation paths.\nIf nil, standard OAuth 2.0 token response parsing is used.\nFor extracting user identity from the token response, see IdentityFromToken.", + "properties": { + "accessTokenPath": { + "description": "AccessTokenPath is the dot-notation path to the access token in the response.\nExample: \"authed_user.access_token\"", + "minLength": 1, + "type": "string" + }, + "expiresInPath": { + "description": "ExpiresInPath is the dot-notation path to the expires_in value (in seconds).\nIf not specified, defaults to \"expires_in\".", + "type": "string" + }, + "refreshTokenPath": { + "description": "RefreshTokenPath is the dot-notation path to the refresh token in the response.\nIf not specified, defaults to \"refresh_token\".", + "type": "string" + }, + "scopePath": { + "description": "ScopePath is the dot-notation path to the scope string in the response.\nIf not specified, defaults to \"scope\".", + "type": "string" + } + }, + "required": [ + "accessTokenPath" + ], + "type": "object", + "additionalProperties": false + }, + "userInfo": { + "description": "UserInfo contains configuration for fetching user information from the upstream provider.\nWhen omitted and IdentityFromToken is also unset, the embedded auth server runs in\nsynthesis mode for this upstream: a non-PII subject derived from the access token, no\nName/Email. Use this shape for upstreams with no userinfo surface and no identity in\nthe token response (e.g., MCP authorization servers per the MCP spec). When\nIdentityFromToken is set instead, identity is resolved from the token response body\n(e.g., Snowflake's \"username\" field, Slack's \"authed_user.id\"); the userinfo HTTP call\nis skipped entirely.", + "properties": { + "additionalHeaders": { + "additionalProperties": { + "type": "string" + }, + "description": "AdditionalHeaders contains extra headers to include in the userinfo request.\nUseful for providers that require specific headers (e.g., GitHub's Accept header).", + "type": "object" + }, + "endpointUrl": { + "description": "EndpointURL is the URL of the userinfo endpoint.", + "pattern": "^https?://.*$", + "type": "string" + }, + "fieldMapping": { + "description": "FieldMapping contains custom field mapping configuration for non-standard providers.\nIf nil, standard OIDC field names are used (\"sub\", \"name\", \"email\").", + "properties": { + "emailFields": { + "description": "EmailFields is an ordered list of field names to try for the email address.\nThe first non-empty value found will be used.\nDefault: [\"email\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "nameFields": { + "description": "NameFields is an ordered list of field names to try for the display name.\nThe first non-empty value found will be used.\nDefault: [\"name\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "subjectFields": { + "description": "SubjectFields is an ordered list of field names to try for the user ID.\nThe first non-empty value found will be used.\nDefault: [\"sub\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "type": "object", + "additionalProperties": false + }, + "httpMethod": { + "description": "HTTPMethod is the HTTP method to use for the userinfo request.\nIf not specified, defaults to GET.", + "enum": [ + "GET", + "POST" + ], + "type": "string" + } + }, + "required": [ + "endpointUrl" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "authorizationEndpoint", + "tokenEndpoint" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "exactly one of clientId or dcrConfig must be set", + "rule": "(has(self.clientId) && size(self.clientId) > 0) ? !has(self.dcrConfig) : has(self.dcrConfig)" + }, + { + "message": "clientSecretRef must not be set when dcrConfig is set; the client_secret is obtained at runtime via Dynamic Client Registration", + "rule": "!(has(self.dcrConfig) && has(self.clientSecretRef))" + } + ], + "additionalProperties": false + }, + "oidcConfig": { + "description": "OIDCConfig contains OIDC-specific configuration.\nRequired when Type is \"oidc\", must be nil when Type is \"oauth2\".", + "properties": { + "additionalAuthorizationParams": { + "additionalProperties": { + "type": "string" + }, + "description": "AdditionalAuthorizationParams are extra query parameters to include in\nauthorization requests sent to the upstream provider.\nThis is useful for providers that require custom parameters, such as\nGoogle's access_type=offline for obtaining refresh tokens.\nNote: when using access_type=offline, also set explicit scopes to avoid\nthe default offline_access scope being sent alongside it.\nFramework-managed parameters (response_type, client_id, redirect_uri,\nscope, state, code_challenge, code_challenge_method, nonce) are not allowed.", + "maxProperties": 16, + "type": "object" + }, + "clientId": { + "description": "ClientID is the OAuth 2.0 client identifier registered with the upstream IDP.", + "type": "string" + }, + "clientSecretRef": { + "description": "ClientSecretRef references a Kubernetes Secret containing the OAuth 2.0 client secret.\nOptional for public clients using PKCE instead of client secret.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "issuerUrl": { + "description": "IssuerURL is the OIDC issuer URL for automatic endpoint discovery.\nMust be a valid HTTPS URL.", + "pattern": "^https://.*$", + "type": "string" + }, + "redirectUri": { + "description": "RedirectURI is the callback URL where the upstream IDP will redirect after authentication.\nWhen not specified, defaults to `{resourceUrl}/oauth/callback` where `resourceUrl` is the\nURL associated with the resource (e.g., MCPServer or vMCP) using this config.", + "type": "string" + }, + "scopes": { + "description": "Scopes are the OAuth scopes to request from the upstream IDP.\nIf not specified, defaults to [\"openid\", \"offline_access\"].\nWhen using additionalAuthorizationParams with provider-specific refresh token\nmechanisms (e.g., Google's access_type=offline), set explicit scopes to avoid\nsending both offline_access and the provider-specific parameter.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "userInfoOverride": { + "description": "UserInfoOverride allows customizing UserInfo fetching behavior for OIDC providers.\nBy default, the UserInfo endpoint is discovered automatically via OIDC discovery.\nUse this to override the endpoint URL, HTTP method, or field mappings for providers\nthat return non-standard claim names in their UserInfo response.", + "properties": { + "additionalHeaders": { + "additionalProperties": { + "type": "string" + }, + "description": "AdditionalHeaders contains extra headers to include in the userinfo request.\nUseful for providers that require specific headers (e.g., GitHub's Accept header).", + "type": "object" + }, + "endpointUrl": { + "description": "EndpointURL is the URL of the userinfo endpoint.", + "pattern": "^https?://.*$", + "type": "string" + }, + "fieldMapping": { + "description": "FieldMapping contains custom field mapping configuration for non-standard providers.\nIf nil, standard OIDC field names are used (\"sub\", \"name\", \"email\").", + "properties": { + "emailFields": { + "description": "EmailFields is an ordered list of field names to try for the email address.\nThe first non-empty value found will be used.\nDefault: [\"email\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "nameFields": { + "description": "NameFields is an ordered list of field names to try for the display name.\nThe first non-empty value found will be used.\nDefault: [\"name\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "subjectFields": { + "description": "SubjectFields is an ordered list of field names to try for the user ID.\nThe first non-empty value found will be used.\nDefault: [\"sub\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "type": "object", + "additionalProperties": false + }, + "httpMethod": { + "description": "HTTPMethod is the HTTP method to use for the userinfo request.\nIf not specified, defaults to GET.", + "enum": [ + "GET", + "POST" + ], + "type": "string" + } + }, + "required": [ + "endpointUrl" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "clientId", + "issuerUrl" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type specifies the provider type: \"oidc\" or \"oauth2\"", + "enum": [ + "oidc", + "oauth2" + ], + "type": "string" + } + }, + "required": [ + "name", + "type" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "type must be 'oidc' or 'oauth2'; oidcConfig must be set when type is 'oidc' and oauth2Config must be set when type is 'oauth2' (and the other must not be set)", + "rule": "self.type == 'oidc' ? (has(self.oidcConfig) && !has(self.oauth2Config)) : self.type == 'oauth2' ? (has(self.oauth2Config) && !has(self.oidcConfig)) : false" + } + ], + "additionalProperties": false + }, + "minItems": 1, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "required": [ + "issuer", + "upstreamProviders" + ], + "type": "object", + "additionalProperties": false + }, + "headerInjection": { + "description": "HeaderInjection configures custom HTTP header injection\nOnly used when Type is \"headerInjection\"", + "properties": { + "headerName": { + "description": "HeaderName is the name of the HTTP header to inject", + "minLength": 1, + "type": "string" + }, + "valueSecretRef": { + "description": "ValueSecretRef references a Kubernetes Secret containing the header value", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "headerName", + "valueSecretRef" + ], + "type": "object", + "additionalProperties": false + }, + "tokenExchange": { + "description": "TokenExchange configures RFC-8693 OAuth 2.0 Token Exchange\nOnly used when Type is \"tokenExchange\"", + "properties": { + "audience": { + "description": "Audience is the target audience for the exchanged token", + "type": "string" + }, + "clientId": { + "description": "ClientID is the OAuth 2.0 client identifier\nOptional for some token exchange flows (e.g., Google Cloud Workforce Identity)", + "type": "string" + }, + "clientSecretRef": { + "description": "ClientSecretRef is a reference to a secret containing the OAuth 2.0 client secret\nOptional for some token exchange flows (e.g., Google Cloud Workforce Identity)", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "externalTokenHeaderName": { + "description": "ExternalTokenHeaderName is the name of the custom header to use for the exchanged token.\nIf set, the exchanged token will be added to this custom header (e.g., \"X-Upstream-Token\").\nIf empty or not set, the exchanged token will replace the Authorization header (default behavior).", + "type": "string" + }, + "scopes": { + "description": "Scopes is a list of OAuth 2.0 scopes to request for the exchanged token", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "subjectProviderName": { + "description": "SubjectProviderName is the name of the upstream provider whose token is used as the\nRFC 8693 subject token instead of identity.Token when performing token exchange.\nWhen left empty and an embedded authorization server is configured on the VirtualMCPServer,\nthe controller automatically populates this field with the first configured upstream\nprovider name. Set it explicitly to override that default or to select a specific\nprovider when multiple upstreams are configured.", + "type": "string" + }, + "subjectTokenType": { + "description": "SubjectTokenType is the type of the incoming subject token.\nAccepts short forms: \"access_token\" (default), \"id_token\", \"jwt\"\nOr full URNs: \"urn:ietf:params:oauth:token-type:access_token\",\n \"urn:ietf:params:oauth:token-type:id_token\",\n \"urn:ietf:params:oauth:token-type:jwt\"\nFor Google Workload Identity Federation with OIDC providers (like Okta), use \"id_token\"", + "pattern": "^(access_token|id_token|jwt|urn:ietf:params:oauth:token-type:(access_token|id_token|jwt))?$", + "type": "string" + }, + "tokenUrl": { + "description": "TokenURL is the OAuth 2.0 token endpoint URL for token exchange", + "type": "string" + } + }, + "required": [ + "audience", + "tokenUrl" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type is the type of external authentication to configure", + "enum": [ + "tokenExchange", + "headerInjection", + "bearerToken", + "unauthenticated", + "embeddedAuthServer", + "awsSts", + "upstreamInject" + ], + "type": "string" + }, + "upstreamInject": { + "description": "UpstreamInject configures upstream token injection for backend requests.\nOnly used when Type is \"upstreamInject\".", + "properties": { + "providerName": { + "description": "ProviderName is the name of the upstream IDP provider whose access token\nshould be injected as the Authorization: Bearer header.", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "providerName" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "type" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "tokenExchange configuration must be set if and only if type is 'tokenExchange'", + "rule": "self.type == 'tokenExchange' ? has(self.tokenExchange) : !has(self.tokenExchange)" + }, + { + "message": "headerInjection configuration must be set if and only if type is 'headerInjection'", + "rule": "self.type == 'headerInjection' ? has(self.headerInjection) : !has(self.headerInjection)" + }, + { + "message": "bearerToken configuration must be set if and only if type is 'bearerToken'", + "rule": "self.type == 'bearerToken' ? has(self.bearerToken) : !has(self.bearerToken)" + }, + { + "message": "embeddedAuthServer configuration must be set if and only if type is 'embeddedAuthServer'", + "rule": "self.type == 'embeddedAuthServer' ? has(self.embeddedAuthServer) : !has(self.embeddedAuthServer)" + }, + { + "message": "awsSts configuration must be set if and only if type is 'awsSts'", + "rule": "self.type == 'awsSts' ? has(self.awsSts) : !has(self.awsSts)" + }, + { + "message": "upstreamInject configuration must be set if and only if type is 'upstreamInject'", + "rule": "self.type == 'upstreamInject' ? has(self.upstreamInject) : !has(self.upstreamInject)" + }, + { + "message": "no configuration must be set when type is 'unauthenticated'", + "rule": "self.type == 'unauthenticated' ? (!has(self.tokenExchange) && !has(self.headerInjection) && !has(self.bearerToken) && !has(self.embeddedAuthServer) && !has(self.awsSts) && !has(self.upstreamInject)) : true" + } + ], + "additionalProperties": false + }, + "status": { + "description": "MCPExternalAuthConfigStatus defines the observed state of MCPExternalAuthConfig", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations of the MCPExternalAuthConfig's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "configHash": { + "description": "ConfigHash is a hash of the current configuration for change detection", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration is the most recent generation observed for this MCPExternalAuthConfig.\nIt corresponds to the MCPExternalAuthConfig's generation, which is updated on mutation by the API Server.", + "format": "int64", + "type": "integer" + }, + "referenceCount": { + "description": "ReferenceCount is the number of workloads referencing this config.", + "format": "int32", + "type": "integer" + }, + "referencingWorkloads": { + "description": "ReferencingWorkloads is a list of workload resources that reference this MCPExternalAuthConfig.\nEach entry identifies the workload by kind and name.", + "items": { + "description": "WorkloadReference identifies a workload that references a shared configuration resource.\nNamespace is implicit \u2014 cross-namespace references are not supported.", + "properties": { + "kind": { + "description": "Kind is the type of workload resource", + "enum": [ + "MCPServer", + "VirtualMCPServer", + "MCPRemoteProxy" + ], + "type": "string" + }, + "name": { + "description": "Name is the name of the workload resource", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/master-standalone/mcpexternalauthconfig-stable-v1beta1.json b/crdSchemas/master-standalone/mcpexternalauthconfig-stable-v1beta1.json new file mode 100644 index 0000000..e9639a0 --- /dev/null +++ b/crdSchemas/master-standalone/mcpexternalauthconfig-stable-v1beta1.json @@ -0,0 +1,1147 @@ +{ + "description": "MCPExternalAuthConfig is the Schema for the mcpexternalauthconfigs API.\nMCPExternalAuthConfig resources are namespace-scoped and can only be referenced by\nMCPServer resources within the same namespace. Cross-namespace references\nare not supported for security and isolation reasons.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPExternalAuthConfigSpec defines the desired state of MCPExternalAuthConfig.\nMCPExternalAuthConfig resources are namespace-scoped and can only be referenced by\nMCPServer resources in the same namespace.", + "properties": { + "awsSts": { + "description": "AWSSts configures AWS STS authentication with SigV4 request signing\nOnly used when Type is \"awsSts\"", + "properties": { + "fallbackRoleArn": { + "description": "FallbackRoleArn is the IAM role ARN to assume when no role mappings match\nUsed as the default role when RoleMappings is empty or no mapping matches\nAt least one of FallbackRoleArn or RoleMappings must be configured (enforced by webhook)", + "pattern": "^arn:(aws|aws-cn|aws-us-gov):iam::\\d{12}:role/[\\w+=,.@\\-_/]+$", + "type": "string" + }, + "region": { + "description": "Region is the AWS region for the STS endpoint and service (e.g., \"us-east-1\", \"eu-west-1\")", + "minLength": 1, + "pattern": "^[a-z]{2}(-[a-z]+)+-\\d+$", + "type": "string" + }, + "roleClaim": { + "default": "groups", + "description": "RoleClaim is the JWT claim to use for role mapping evaluation\nDefaults to \"groups\" to match common OIDC group claims", + "type": "string" + }, + "roleMappings": { + "description": "RoleMappings defines claim-based role selection rules\nAllows mapping JWT claims (e.g., groups, roles) to specific IAM roles\nLower priority values are evaluated first (higher priority)", + "items": { + "description": "RoleMapping defines a rule for mapping JWT claims to IAM roles.\nMappings are evaluated in priority order (lower number = higher priority), and the first\nmatching rule determines which IAM role to assume.\nExactly one of Claim or Matcher must be specified.", + "properties": { + "claim": { + "description": "Claim is a simple claim value to match against\nThe claim type is specified by AWSStsConfig.RoleClaim\nFor example, if RoleClaim is \"groups\", this would be a group name\nInternally compiled to a CEL expression: \"\" in claims[\"\"]\nMutually exclusive with Matcher", + "minLength": 1, + "type": "string" + }, + "matcher": { + "description": "Matcher is a CEL expression for complex matching against JWT claims\nThe expression has access to a \"claims\" variable containing all JWT claims as map[string]any\nExamples:\n - \"admins\" in claims[\"groups\"]\n - claims[\"sub\"] == \"user123\" && !(\"act\" in claims)\nMutually exclusive with Claim", + "minLength": 1, + "type": "string" + }, + "priority": { + "description": "Priority determines evaluation order (lower values = higher priority)\nAllows fine-grained control over role selection precedence\nWhen omitted, this mapping has the lowest possible priority and\nconfiguration order acts as tie-breaker via stable sort", + "format": "int32", + "minimum": 0, + "type": "integer" + }, + "roleArn": { + "description": "RoleArn is the IAM role ARN to assume when this mapping matches", + "pattern": "^arn:(aws|aws-cn|aws-us-gov):iam::\\d{12}:role/[\\w+=,.@\\-_/]+$", + "type": "string" + } + }, + "required": [ + "roleArn" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "service": { + "default": "aws-mcp", + "description": "Service is the AWS service name for SigV4 signing\nDefaults to \"aws-mcp\" for AWS MCP Server endpoints", + "type": "string" + }, + "sessionDuration": { + "default": 3600, + "description": "SessionDuration is the duration in seconds for the STS session\nMust be between 900 (15 minutes) and 43200 (12 hours)\nDefaults to 3600 (1 hour) if not specified", + "format": "int32", + "maximum": 43200, + "minimum": 900, + "type": "integer" + }, + "sessionNameClaim": { + "default": "sub", + "description": "SessionNameClaim is the JWT claim to use for role session name\nDefaults to \"sub\" to use the subject claim", + "type": "string" + }, + "subjectProviderName": { + "description": "SubjectProviderName is the name of the upstream provider whose access token\nis used as the web identity token for STS AssumeRoleWithWebIdentity.\nThis field is used exclusively by VirtualMCPServer, where there is no\nupstream swap middleware to replace the bearer token before the strategy runs.\nWhen left empty and an embedded authorization server is configured on the\nVirtualMCPServer, the controller automatically populates this field with\nthe first configured upstream provider name. Set it explicitly to override\nthat default or to select a specific provider when multiple upstreams are\nconfigured.\nWhen no embedded auth server is present, the bearer token from the incoming\nrequest's Authorization header is used instead.", + "type": "string" + } + }, + "required": [ + "region" + ], + "type": "object", + "additionalProperties": false + }, + "bearerToken": { + "description": "BearerToken configures bearer token authentication\nOnly used when Type is \"bearerToken\"", + "properties": { + "tokenSecretRef": { + "description": "TokenSecretRef references a Kubernetes Secret containing the bearer token", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "tokenSecretRef" + ], + "type": "object", + "additionalProperties": false + }, + "embeddedAuthServer": { + "description": "EmbeddedAuthServer configures an embedded OAuth2/OIDC authorization server\nOnly used when Type is \"embeddedAuthServer\"", + "properties": { + "authorizationEndpointBaseUrl": { + "description": "AuthorizationEndpointBaseURL overrides the base URL used for the authorization_endpoint\nin the OAuth discovery document. When set, the discovery document will advertise\n`{authorizationEndpointBaseUrl}/oauth/authorize` instead of `{issuer}/oauth/authorize`.\nAll other endpoints (token, registration, JWKS) remain derived from the issuer.\nThis is useful when the browser-facing authorization endpoint needs to be on a\ndifferent host than the issuer used for backend-to-backend calls.\nMust be a valid HTTPS URL (or HTTP for localhost) without query, fragment, or trailing slash.", + "pattern": "^https?://[^\\s?#]+[^/\\s?#]$", + "type": "string" + }, + "baselineClientScopes": { + "description": "BaselineClientScopes is a baseline set of OAuth 2.0 scopes guaranteed to be\nincluded in every client registration. The embedded auth server unions these\nscopes into the registered set returned by RFC 7591 Dynamic Client\nRegistration, so a client that narrows the `scope` field at /oauth/register\ncan still request the baseline scopes at /oauth/authorize. All values must\nbe present in the upstream-derived scopesSupported set; the auth server\nfails to start if any value is missing.\n\nSecurity: every client registered via /oauth/register will gain the\nability to request these scopes at /oauth/authorize, regardless of what\nthe client itself requested. Keep the baseline narrow (typically\n\"openid\" and \"offline_access\"). Adding a privileged scope here \u2014 e.g.\n\"admin:read\" \u2014 would grant it to every DCR-registered client, including\npublic clients like Claude Code, Cursor, and VS Code.", + "items": { + "minLength": 1, + "pattern": "^[\\x21\\x23-\\x5B\\x5D-\\x7E]+$", + "type": "string" + }, + "maxItems": 10, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "hmacSecretRefs": { + "description": "HMACSecretRefs references Kubernetes Secrets containing symmetric secrets for signing\nauthorization codes and refresh tokens (opaque tokens).\nCurrent secret must be at least 32 bytes and cryptographically random.\nSupports secret rotation via multiple entries (first is current, rest are for verification).\nIf not specified, an ephemeral secret will be auto-generated (development only -\nauth codes and refresh tokens will be invalid after restart).", + "items": { + "description": "SecretKeyRef is a reference to a key within a Secret", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "issuer": { + "description": "Issuer is the issuer identifier for this authorization server.\nThis will be included in the \"iss\" claim of issued tokens.\nMust be a valid HTTPS URL (or HTTP for localhost) without query, fragment, or trailing slash (per RFC 8414).", + "pattern": "^https?://[^\\s?#]+[^/\\s?#]$", + "type": "string" + }, + "primaryUpstreamProvider": { + "description": "PrimaryUpstreamProvider names the upstream IDP whose access token Cedar\nshould read claims from when authorising a request. Must match the name\nof one of the entries in UpstreamProviders. When empty, the controller\nauto-selects the first entry of UpstreamProviders.\n\nOnly meaningful on VirtualMCPServer, where multiple upstream providers\ncan be configured and Cedar needs to pick which token's claims to\nevaluate. The VirtualMCPServer controller validates this field against\nUpstreamProviders at admission and rejects unresolvable values.\n\nOn MCPServer and MCPRemoteProxy this field is structurally present (the\nEmbeddedAuthServerConfig struct is shared) but has no runtime effect:\nthose CRDs are restricted to a single upstream so there is no choice to\nmake. Setting it on those CRDs is silently ignored.", + "maxLength": 63, + "minLength": 1, + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "type": "string" + }, + "signingKeySecretRefs": { + "description": "SigningKeySecretRefs references Kubernetes Secrets containing signing keys for JWT operations.\nSupports key rotation by allowing multiple keys (oldest keys are used for verification only).\nIf not specified, an ephemeral signing key will be auto-generated (development only -\nJWTs will be invalid after restart).", + "items": { + "description": "SecretKeyRef is a reference to a key within a Secret", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "maxItems": 5, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "storage": { + "description": "Storage configures the storage backend for the embedded auth server.\nIf not specified, defaults to in-memory storage.", + "properties": { + "redis": { + "description": "Redis configures the Redis storage backend.\nRequired when type is \"redis\".", + "properties": { + "aclUserConfig": { + "description": "ACLUserConfig configures Redis ACL user authentication.", + "properties": { + "passwordSecretRef": { + "description": "PasswordSecretRef references a Secret containing the Redis ACL password.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "usernameSecretRef": { + "description": "UsernameSecretRef references a Secret containing the Redis ACL username.\nWhen omitted, connections use legacy password-only AUTH. Omit for managed\nRedis tiers that do not support ACL users (e.g. GCP Memorystore Basic/Standard\nHA, Azure Cache for Redis). Set for services that support ACL users (e.g. AWS\nElastiCache non-cluster with Redis 6+ RBAC).", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "passwordSecretRef" + ], + "type": "object", + "additionalProperties": false + }, + "addr": { + "description": "Addr is the Redis server address (host:port). Required for standalone and cluster modes.\nUse for managed Redis services that expose a single endpoint (GCP Memorystore basic tier,\nAWS ElastiCache without cluster mode, or cluster-mode services when clusterMode is true).\nMutually exclusive with sentinelConfig.", + "type": "string" + }, + "clusterMode": { + "description": "ClusterMode enables the Redis Cluster protocol. Set to true when addr points to a\nRedis Cluster discovery endpoint (e.g., GCP Memorystore Cluster, AWS ElastiCache\ncluster mode enabled). Requires addr to be set.", + "type": "boolean" + }, + "dialTimeout": { + "default": "5s", + "description": "DialTimeout is the timeout for establishing connections.\nFormat: Go duration string (e.g., \"5s\", \"1m\").", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "readTimeout": { + "default": "3s", + "description": "ReadTimeout is the timeout for socket reads.\nFormat: Go duration string (e.g., \"3s\", \"1m\").", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "sentinelConfig": { + "description": "SentinelConfig holds Redis Sentinel configuration.\nUse for self-managed Redis with Sentinel-based HA. Mutually exclusive with addr.", + "properties": { + "db": { + "default": 0, + "description": "DB is the Redis database number.", + "format": "int32", + "type": "integer" + }, + "masterName": { + "description": "MasterName is the name of the Redis master monitored by Sentinel.", + "type": "string" + }, + "sentinelAddrs": { + "description": "SentinelAddrs is a list of Sentinel host:port addresses.\nMutually exclusive with SentinelService.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "sentinelService": { + "description": "SentinelService enables automatic discovery from a Kubernetes Service.\nMutually exclusive with SentinelAddrs.", + "properties": { + "name": { + "description": "Name of the Sentinel Service.", + "type": "string" + }, + "namespace": { + "description": "Namespace of the Sentinel Service (defaults to same namespace).", + "type": "string" + }, + "port": { + "default": 26379, + "description": "Port of the Sentinel service.", + "format": "int32", + "type": "integer" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "masterName" + ], + "type": "object", + "additionalProperties": false + }, + "sentinelTls": { + "description": "SentinelTLS configures TLS for connections to Sentinel instances.\nOnly applies when sentinelConfig is set. Presence of this field enables TLS.", + "properties": { + "caCertSecretRef": { + "description": "CACertSecretRef references a Secret containing a PEM-encoded CA certificate\nfor verifying the server. When not specified, system root CAs are used.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "insecureSkipVerify": { + "description": "InsecureSkipVerify skips TLS certificate verification.\nUse when connecting to services with self-signed certificates.", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "tls": { + "description": "TLS configures TLS for connections to the Redis/Valkey master.\nPresence of this field enables TLS. Omit to use plaintext.", + "properties": { + "caCertSecretRef": { + "description": "CACertSecretRef references a Secret containing a PEM-encoded CA certificate\nfor verifying the server. When not specified, system root CAs are used.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "insecureSkipVerify": { + "description": "InsecureSkipVerify skips TLS certificate verification.\nUse when connecting to services with self-signed certificates.", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "writeTimeout": { + "default": "3s", + "description": "WriteTimeout is the timeout for socket writes.\nFormat: Go duration string (e.g., \"3s\", \"1m\").", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + } + }, + "required": [ + "aclUserConfig" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "exactly one of addr or sentinelConfig must be set", + "rule": "(has(self.addr) && self.addr.size() > 0) != has(self.sentinelConfig)" + }, + { + "message": "clusterMode requires addr to be set", + "rule": "!(has(self.clusterMode) && self.clusterMode) || (has(self.addr) && self.addr.size() > 0)" + } + ], + "additionalProperties": false + }, + "type": { + "default": "memory", + "description": "Type specifies the storage backend type.\nValid values: \"memory\" (default), \"redis\".", + "enum": [ + "memory", + "redis" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "tokenLifespans": { + "description": "TokenLifespans configures the duration that various tokens are valid.\nIf not specified, defaults are applied (access: 1h, refresh: 7d, authCode: 10m).", + "properties": { + "accessTokenLifespan": { + "description": "AccessTokenLifespan is the duration that access tokens are valid.\nFormat: Go duration string (e.g., \"1h\", \"30m\", \"24h\").\nIf empty, defaults to 1 hour.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "authCodeLifespan": { + "description": "AuthCodeLifespan is the duration that authorization codes are valid.\nFormat: Go duration string (e.g., \"10m\", \"5m\").\nIf empty, defaults to 10 minutes.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "refreshTokenLifespan": { + "description": "RefreshTokenLifespan is the duration that refresh tokens are valid.\nFormat: Go duration string (e.g., \"168h\", \"7d\" as \"168h\").\nIf empty, defaults to 7 days (168h).", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "upstreamProviders": { + "description": "UpstreamProviders configures connections to upstream Identity Providers.\nThe embedded auth server delegates authentication to these providers.\nMCPServer and MCPRemoteProxy support a single upstream; VirtualMCPServer supports multiple.", + "items": { + "description": "UpstreamProviderConfig defines configuration for an upstream Identity Provider.\n\nExactly one of OIDCConfig or OAuth2Config must be set and must match the\ndeclared Type: oidc-typed providers set OIDCConfig, oauth2-typed providers\nset OAuth2Config. The CEL rule below enforces the pairing at admission; the\nmatching Go-level check in validateUpstreamProvider provides defense-in-depth\nfor stored objects.\n\nThe rule is structured as a chain of equality checks ending in an explicit\n`false`, so adding a new UpstreamProviderType value without extending this\nrule fails admission instead of silently demanding the OAuth2 shape. When\nadding a new type, extend both this rule and validateUpstreamProvider.", + "properties": { + "name": { + "description": "Name uniquely identifies this upstream provider.\nUsed for routing decisions and session binding in multi-upstream scenarios.\nMust be lowercase alphanumeric with hyphens (DNS-label-like).", + "maxLength": 63, + "minLength": 1, + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "type": "string" + }, + "oauth2Config": { + "description": "OAuth2Config contains OAuth 2.0-specific configuration.\nRequired when Type is \"oauth2\", must be nil when Type is \"oidc\".", + "properties": { + "additionalAuthorizationParams": { + "additionalProperties": { + "type": "string" + }, + "description": "AdditionalAuthorizationParams are extra query parameters to include in\nauthorization requests sent to the upstream provider.\nThis is useful for providers that require custom parameters, such as\nGoogle's access_type=offline for obtaining refresh tokens.\nFramework-managed parameters (response_type, client_id, redirect_uri,\nscope, state, code_challenge, code_challenge_method, nonce) are not allowed.", + "maxProperties": 16, + "type": "object" + }, + "authorizationEndpoint": { + "description": "AuthorizationEndpoint is the URL for the OAuth authorization endpoint.", + "pattern": "^https?://.*$", + "type": "string" + }, + "clientId": { + "description": "ClientID is the OAuth 2.0 client identifier registered with the upstream IDP.\nMutually exclusive with DCRConfig: when DCRConfig is set, ClientID is obtained\nat runtime via RFC 7591 Dynamic Client Registration and must be left empty.", + "type": "string" + }, + "clientSecretRef": { + "description": "ClientSecretRef references a Kubernetes Secret containing the OAuth 2.0 client secret.\nOptional for public clients using PKCE instead of client secret.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "dcrConfig": { + "description": "DCRConfig enables RFC 7591 Dynamic Client Registration against the upstream\nauthorization server. When set, the client credentials are obtained at\nruntime rather than being pre-provisioned, and ClientID must be left empty.\nMutually exclusive with ClientID.", + "properties": { + "discoveryUrl": { + "description": "DiscoveryURL is the RFC 8414 / OIDC Discovery document URL. The resolver\nissues a single GET against this URL (no well-known-path fallback) and\nreads registration_endpoint, authorization_endpoint, token_endpoint,\ntoken_endpoint_auth_methods_supported, and scopes_supported from the\nresponse.\nMutually exclusive with RegistrationEndpoint.\nHTTPS is required because the registration endpoint resolved from this\ndocument carries the initial access token and the issued client_secret\n(RFC 7591 \u00a73, RFC 8414 \u00a73). MaxLength is a defensive size cap (etcd\nobject budget, regex evaluation cost) and matches the conventional URL\nlength cap.", + "maxLength": 2048, + "pattern": "^https://[^\\s?#]+[^/\\s?#]$", + "type": "string" + }, + "initialAccessTokenRef": { + "description": "InitialAccessTokenRef is an optional reference to a Kubernetes Secret\ncarrying an RFC 7591 \u00a73 initial access token. When set, the resolver\npresents the token value as a Bearer credential on the registration\nrequest. Mirrors the ClientSecretRef pattern.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "registrationEndpoint": { + "description": "RegistrationEndpoint is the RFC 7591 registration endpoint URL used\ndirectly, bypassing discovery. When using this field, the caller is\nexpected to also supply AuthorizationEndpoint, TokenEndpoint, and an\nexplicit Scopes list on the parent OAuth2UpstreamConfig.\nMutually exclusive with DiscoveryURL.\nHTTPS is required because the registration endpoint carries the initial\naccess token and the issued client_secret (RFC 7591 \u00a73, RFC 8414 \u00a73).\nMaxLength is a defensive size cap (etcd object budget, regex evaluation\ncost) and matches the conventional URL length cap.", + "maxLength": 2048, + "pattern": "^https://[^\\s?#]+[^/\\s?#]$", + "type": "string" + }, + "softwareId": { + "description": "SoftwareID is the RFC 7591 \"software_id\" registration metadata value,\nidentifying the client software independent of any particular\nregistration instance. Typically a UUID or short identifier.", + "maxLength": 255, + "type": "string" + }, + "softwareStatement": { + "description": "SoftwareStatement is the RFC 7591 \"software_statement\" JWT asserting\nmetadata about the client software, signed by a party the authorization\nserver trusts.\n\nStored inline on the CR. The JWT is signed but not encrypted, so its\ncontents are visible to anyone with get/list/watch on this resource and\nappear in etcd backups in plaintext. Treat the value as non-confidential\n(signed attestation, not a secret). Operators that rotate software\nstatements like bearer credentials should keep them at the authorization\nserver side and rely on the registration endpoint's initial access\ntoken (see InitialAccessTokenRef) instead of placing them on the CR.\n\nBounded to 16384 characters as a defensive size cap (etcd object\nbudget, regex evaluation cost). Real-world signed statements with\nembedded x5c certificate chains, JWKS keys, or OIDC-Federation\ntrust-framework metadata routinely exceed 4 KB.", + "maxLength": 16384, + "type": "string" + } + }, + "type": "object", + "x-kubernetes-validations": [ + { + "message": "exactly one of discoveryUrl or registrationEndpoint must be set", + "rule": "has(self.discoveryUrl) != has(self.registrationEndpoint)" + } + ], + "additionalProperties": false + }, + "identityFromToken": { + "description": "IdentityFromToken extracts user identity (subject, name, email) directly\nfrom the OAuth2 token-endpoint response body using gjson dot-notation paths.\nWhen set, the embedded auth server skips the userinfo HTTP call entirely\nand resolves identity from the token response. See IdentityFromTokenConfig\nfor trust-model and uniqueness considerations.", + "properties": { + "emailPath": { + "description": "EmailPath is the dot-notation path to the email address field in the token response.\nIf not specified or if the path does not resolve to a string, the email is omitted.\nOmit the field entirely rather than setting it to an empty string.", + "maxLength": 256, + "minLength": 1, + "type": "string" + }, + "namePath": { + "description": "NamePath is the dot-notation path to the display name field in the token response.\nIf not specified or if the path does not resolve to a string, the display name is omitted.\nOmit the field entirely rather than setting it to an empty string.", + "maxLength": 256, + "minLength": 1, + "type": "string" + }, + "subjectPath": { + "description": "SubjectPath is the dot-notation path to the subject (user ID) field in the token response.\nWarning: claims read from the token response are trusted only via TLS, not\ncryptographically verified; prefer OIDC ID tokens when verifiable claims are required.\nExample: \"authed_user.id\" for Slack (top-level token-response field). For providers\nwhose token response embeds the access token as a JWT (e.g. Snowflake), use the\n\"@upstreamjwt\" modifier to decode the payload, e.g. \"access_token|@upstreamjwt|sub\".\nThe \"@upstreamjwt\" modifier performs no signature verification either.", + "maxLength": 256, + "minLength": 1, + "type": "string" + } + }, + "required": [ + "subjectPath" + ], + "type": "object", + "additionalProperties": false + }, + "redirectUri": { + "description": "RedirectURI is the callback URL where the upstream IDP will redirect after authentication.\nWhen not specified, defaults to `{resourceUrl}/oauth/callback` where `resourceUrl` is the\nURL associated with the resource (e.g., MCPServer or vMCP) using this config.", + "type": "string" + }, + "scopes": { + "description": "Scopes are the OAuth scopes to request from the upstream IDP.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "tokenEndpoint": { + "description": "TokenEndpoint is the URL for the OAuth token endpoint.", + "pattern": "^https?://.*$", + "type": "string" + }, + "tokenResponseMapping": { + "description": "TokenResponseMapping configures custom field extraction from non-standard token responses.\nSome OAuth providers (e.g., GovSlack) nest token fields under non-standard paths\ninstead of returning them at the top level. When set, ToolHive performs the token\nexchange HTTP call directly and extracts fields using the configured dot-notation paths.\nIf nil, standard OAuth 2.0 token response parsing is used.\nFor extracting user identity from the token response, see IdentityFromToken.", + "properties": { + "accessTokenPath": { + "description": "AccessTokenPath is the dot-notation path to the access token in the response.\nExample: \"authed_user.access_token\"", + "minLength": 1, + "type": "string" + }, + "expiresInPath": { + "description": "ExpiresInPath is the dot-notation path to the expires_in value (in seconds).\nIf not specified, defaults to \"expires_in\".", + "type": "string" + }, + "refreshTokenPath": { + "description": "RefreshTokenPath is the dot-notation path to the refresh token in the response.\nIf not specified, defaults to \"refresh_token\".", + "type": "string" + }, + "scopePath": { + "description": "ScopePath is the dot-notation path to the scope string in the response.\nIf not specified, defaults to \"scope\".", + "type": "string" + } + }, + "required": [ + "accessTokenPath" + ], + "type": "object", + "additionalProperties": false + }, + "userInfo": { + "description": "UserInfo contains configuration for fetching user information from the upstream provider.\nWhen omitted and IdentityFromToken is also unset, the embedded auth server runs in\nsynthesis mode for this upstream: a non-PII subject derived from the access token, no\nName/Email. Use this shape for upstreams with no userinfo surface and no identity in\nthe token response (e.g., MCP authorization servers per the MCP spec). When\nIdentityFromToken is set instead, identity is resolved from the token response body\n(e.g., Snowflake's \"username\" field, Slack's \"authed_user.id\"); the userinfo HTTP call\nis skipped entirely.", + "properties": { + "additionalHeaders": { + "additionalProperties": { + "type": "string" + }, + "description": "AdditionalHeaders contains extra headers to include in the userinfo request.\nUseful for providers that require specific headers (e.g., GitHub's Accept header).", + "type": "object" + }, + "endpointUrl": { + "description": "EndpointURL is the URL of the userinfo endpoint.", + "pattern": "^https?://.*$", + "type": "string" + }, + "fieldMapping": { + "description": "FieldMapping contains custom field mapping configuration for non-standard providers.\nIf nil, standard OIDC field names are used (\"sub\", \"name\", \"email\").", + "properties": { + "emailFields": { + "description": "EmailFields is an ordered list of field names to try for the email address.\nThe first non-empty value found will be used.\nDefault: [\"email\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "nameFields": { + "description": "NameFields is an ordered list of field names to try for the display name.\nThe first non-empty value found will be used.\nDefault: [\"name\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "subjectFields": { + "description": "SubjectFields is an ordered list of field names to try for the user ID.\nThe first non-empty value found will be used.\nDefault: [\"sub\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "type": "object", + "additionalProperties": false + }, + "httpMethod": { + "description": "HTTPMethod is the HTTP method to use for the userinfo request.\nIf not specified, defaults to GET.", + "enum": [ + "GET", + "POST" + ], + "type": "string" + } + }, + "required": [ + "endpointUrl" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "authorizationEndpoint", + "tokenEndpoint" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "exactly one of clientId or dcrConfig must be set", + "rule": "(has(self.clientId) && size(self.clientId) > 0) ? !has(self.dcrConfig) : has(self.dcrConfig)" + }, + { + "message": "clientSecretRef must not be set when dcrConfig is set; the client_secret is obtained at runtime via Dynamic Client Registration", + "rule": "!(has(self.dcrConfig) && has(self.clientSecretRef))" + } + ], + "additionalProperties": false + }, + "oidcConfig": { + "description": "OIDCConfig contains OIDC-specific configuration.\nRequired when Type is \"oidc\", must be nil when Type is \"oauth2\".", + "properties": { + "additionalAuthorizationParams": { + "additionalProperties": { + "type": "string" + }, + "description": "AdditionalAuthorizationParams are extra query parameters to include in\nauthorization requests sent to the upstream provider.\nThis is useful for providers that require custom parameters, such as\nGoogle's access_type=offline for obtaining refresh tokens.\nNote: when using access_type=offline, also set explicit scopes to avoid\nthe default offline_access scope being sent alongside it.\nFramework-managed parameters (response_type, client_id, redirect_uri,\nscope, state, code_challenge, code_challenge_method, nonce) are not allowed.", + "maxProperties": 16, + "type": "object" + }, + "clientId": { + "description": "ClientID is the OAuth 2.0 client identifier registered with the upstream IDP.", + "type": "string" + }, + "clientSecretRef": { + "description": "ClientSecretRef references a Kubernetes Secret containing the OAuth 2.0 client secret.\nOptional for public clients using PKCE instead of client secret.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "issuerUrl": { + "description": "IssuerURL is the OIDC issuer URL for automatic endpoint discovery.\nMust be a valid HTTPS URL.", + "pattern": "^https://.*$", + "type": "string" + }, + "redirectUri": { + "description": "RedirectURI is the callback URL where the upstream IDP will redirect after authentication.\nWhen not specified, defaults to `{resourceUrl}/oauth/callback` where `resourceUrl` is the\nURL associated with the resource (e.g., MCPServer or vMCP) using this config.", + "type": "string" + }, + "scopes": { + "description": "Scopes are the OAuth scopes to request from the upstream IDP.\nIf not specified, defaults to [\"openid\", \"offline_access\"].\nWhen using additionalAuthorizationParams with provider-specific refresh token\nmechanisms (e.g., Google's access_type=offline), set explicit scopes to avoid\nsending both offline_access and the provider-specific parameter.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "userInfoOverride": { + "description": "UserInfoOverride allows customizing UserInfo fetching behavior for OIDC providers.\nBy default, the UserInfo endpoint is discovered automatically via OIDC discovery.\nUse this to override the endpoint URL, HTTP method, or field mappings for providers\nthat return non-standard claim names in their UserInfo response.", + "properties": { + "additionalHeaders": { + "additionalProperties": { + "type": "string" + }, + "description": "AdditionalHeaders contains extra headers to include in the userinfo request.\nUseful for providers that require specific headers (e.g., GitHub's Accept header).", + "type": "object" + }, + "endpointUrl": { + "description": "EndpointURL is the URL of the userinfo endpoint.", + "pattern": "^https?://.*$", + "type": "string" + }, + "fieldMapping": { + "description": "FieldMapping contains custom field mapping configuration for non-standard providers.\nIf nil, standard OIDC field names are used (\"sub\", \"name\", \"email\").", + "properties": { + "emailFields": { + "description": "EmailFields is an ordered list of field names to try for the email address.\nThe first non-empty value found will be used.\nDefault: [\"email\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "nameFields": { + "description": "NameFields is an ordered list of field names to try for the display name.\nThe first non-empty value found will be used.\nDefault: [\"name\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "subjectFields": { + "description": "SubjectFields is an ordered list of field names to try for the user ID.\nThe first non-empty value found will be used.\nDefault: [\"sub\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "type": "object", + "additionalProperties": false + }, + "httpMethod": { + "description": "HTTPMethod is the HTTP method to use for the userinfo request.\nIf not specified, defaults to GET.", + "enum": [ + "GET", + "POST" + ], + "type": "string" + } + }, + "required": [ + "endpointUrl" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "clientId", + "issuerUrl" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type specifies the provider type: \"oidc\" or \"oauth2\"", + "enum": [ + "oidc", + "oauth2" + ], + "type": "string" + } + }, + "required": [ + "name", + "type" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "type must be 'oidc' or 'oauth2'; oidcConfig must be set when type is 'oidc' and oauth2Config must be set when type is 'oauth2' (and the other must not be set)", + "rule": "self.type == 'oidc' ? (has(self.oidcConfig) && !has(self.oauth2Config)) : self.type == 'oauth2' ? (has(self.oauth2Config) && !has(self.oidcConfig)) : false" + } + ], + "additionalProperties": false + }, + "minItems": 1, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "required": [ + "issuer", + "upstreamProviders" + ], + "type": "object", + "additionalProperties": false + }, + "headerInjection": { + "description": "HeaderInjection configures custom HTTP header injection\nOnly used when Type is \"headerInjection\"", + "properties": { + "headerName": { + "description": "HeaderName is the name of the HTTP header to inject", + "minLength": 1, + "type": "string" + }, + "valueSecretRef": { + "description": "ValueSecretRef references a Kubernetes Secret containing the header value", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "headerName", + "valueSecretRef" + ], + "type": "object", + "additionalProperties": false + }, + "tokenExchange": { + "description": "TokenExchange configures RFC-8693 OAuth 2.0 Token Exchange\nOnly used when Type is \"tokenExchange\"", + "properties": { + "audience": { + "description": "Audience is the target audience for the exchanged token", + "type": "string" + }, + "clientId": { + "description": "ClientID is the OAuth 2.0 client identifier\nOptional for some token exchange flows (e.g., Google Cloud Workforce Identity)", + "type": "string" + }, + "clientSecretRef": { + "description": "ClientSecretRef is a reference to a secret containing the OAuth 2.0 client secret\nOptional for some token exchange flows (e.g., Google Cloud Workforce Identity)", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "externalTokenHeaderName": { + "description": "ExternalTokenHeaderName is the name of the custom header to use for the exchanged token.\nIf set, the exchanged token will be added to this custom header (e.g., \"X-Upstream-Token\").\nIf empty or not set, the exchanged token will replace the Authorization header (default behavior).", + "type": "string" + }, + "scopes": { + "description": "Scopes is a list of OAuth 2.0 scopes to request for the exchanged token", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "subjectProviderName": { + "description": "SubjectProviderName is the name of the upstream provider whose token is used as the\nRFC 8693 subject token instead of identity.Token when performing token exchange.\nWhen left empty and an embedded authorization server is configured on the VirtualMCPServer,\nthe controller automatically populates this field with the first configured upstream\nprovider name. Set it explicitly to override that default or to select a specific\nprovider when multiple upstreams are configured.", + "type": "string" + }, + "subjectTokenType": { + "description": "SubjectTokenType is the type of the incoming subject token.\nAccepts short forms: \"access_token\" (default), \"id_token\", \"jwt\"\nOr full URNs: \"urn:ietf:params:oauth:token-type:access_token\",\n \"urn:ietf:params:oauth:token-type:id_token\",\n \"urn:ietf:params:oauth:token-type:jwt\"\nFor Google Workload Identity Federation with OIDC providers (like Okta), use \"id_token\"", + "pattern": "^(access_token|id_token|jwt|urn:ietf:params:oauth:token-type:(access_token|id_token|jwt))?$", + "type": "string" + }, + "tokenUrl": { + "description": "TokenURL is the OAuth 2.0 token endpoint URL for token exchange", + "type": "string" + } + }, + "required": [ + "audience", + "tokenUrl" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type is the type of external authentication to configure", + "enum": [ + "tokenExchange", + "headerInjection", + "bearerToken", + "unauthenticated", + "embeddedAuthServer", + "awsSts", + "upstreamInject" + ], + "type": "string" + }, + "upstreamInject": { + "description": "UpstreamInject configures upstream token injection for backend requests.\nOnly used when Type is \"upstreamInject\".", + "properties": { + "providerName": { + "description": "ProviderName is the name of the upstream IDP provider whose access token\nshould be injected as the Authorization: Bearer header.", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "providerName" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "type" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "tokenExchange configuration must be set if and only if type is 'tokenExchange'", + "rule": "self.type == 'tokenExchange' ? has(self.tokenExchange) : !has(self.tokenExchange)" + }, + { + "message": "headerInjection configuration must be set if and only if type is 'headerInjection'", + "rule": "self.type == 'headerInjection' ? has(self.headerInjection) : !has(self.headerInjection)" + }, + { + "message": "bearerToken configuration must be set if and only if type is 'bearerToken'", + "rule": "self.type == 'bearerToken' ? has(self.bearerToken) : !has(self.bearerToken)" + }, + { + "message": "embeddedAuthServer configuration must be set if and only if type is 'embeddedAuthServer'", + "rule": "self.type == 'embeddedAuthServer' ? has(self.embeddedAuthServer) : !has(self.embeddedAuthServer)" + }, + { + "message": "awsSts configuration must be set if and only if type is 'awsSts'", + "rule": "self.type == 'awsSts' ? has(self.awsSts) : !has(self.awsSts)" + }, + { + "message": "upstreamInject configuration must be set if and only if type is 'upstreamInject'", + "rule": "self.type == 'upstreamInject' ? has(self.upstreamInject) : !has(self.upstreamInject)" + }, + { + "message": "no configuration must be set when type is 'unauthenticated'", + "rule": "self.type == 'unauthenticated' ? (!has(self.tokenExchange) && !has(self.headerInjection) && !has(self.bearerToken) && !has(self.embeddedAuthServer) && !has(self.awsSts) && !has(self.upstreamInject)) : true" + } + ], + "additionalProperties": false + }, + "status": { + "description": "MCPExternalAuthConfigStatus defines the observed state of MCPExternalAuthConfig", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations of the MCPExternalAuthConfig's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "configHash": { + "description": "ConfigHash is a hash of the current configuration for change detection", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration is the most recent generation observed for this MCPExternalAuthConfig.\nIt corresponds to the MCPExternalAuthConfig's generation, which is updated on mutation by the API Server.", + "format": "int64", + "type": "integer" + }, + "referenceCount": { + "description": "ReferenceCount is the number of workloads referencing this config.", + "format": "int32", + "type": "integer" + }, + "referencingWorkloads": { + "description": "ReferencingWorkloads is a list of workload resources that reference this MCPExternalAuthConfig.\nEach entry identifies the workload by kind and name.", + "items": { + "description": "WorkloadReference identifies a workload that references a shared configuration resource.\nNamespace is implicit \u2014 cross-namespace references are not supported.", + "properties": { + "kind": { + "description": "Kind is the type of workload resource", + "enum": [ + "MCPServer", + "VirtualMCPServer", + "MCPRemoteProxy" + ], + "type": "string" + }, + "name": { + "description": "Name is the name of the workload resource", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/master-standalone/mcpgroup-stable-v1alpha1.json b/crdSchemas/master-standalone/mcpgroup-stable-v1alpha1.json new file mode 100644 index 0000000..83ab2e4 --- /dev/null +++ b/crdSchemas/master-standalone/mcpgroup-stable-v1alpha1.json @@ -0,0 +1,149 @@ +{ + "description": "MCPGroup is the deprecated v1alpha1 version of the MCPGroup resource.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPGroupSpec defines the desired state of MCPGroup", + "properties": { + "description": { + "description": "Description provides human-readable context", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "status": { + "description": "MCPGroupStatus defines observed state", + "properties": { + "conditions": { + "description": "Conditions represent observations", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "entries": { + "description": "Entries lists MCPServerEntry names in this group", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "set" + }, + "entryCount": { + "description": "EntryCount is the number of MCPServerEntries", + "format": "int32", + "type": "integer" + }, + "observedGeneration": { + "description": "ObservedGeneration reflects the generation most recently observed by the controller", + "format": "int64", + "type": "integer" + }, + "phase": { + "default": "Pending", + "description": "Phase indicates current state", + "enum": [ + "Ready", + "Pending", + "Failed" + ], + "type": "string" + }, + "remoteProxies": { + "description": "RemoteProxies lists MCPRemoteProxy names in this group", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "set" + }, + "remoteProxyCount": { + "description": "RemoteProxyCount is the number of MCPRemoteProxies", + "format": "int32", + "type": "integer" + }, + "serverCount": { + "description": "ServerCount is the number of MCPServers", + "format": "int32", + "type": "integer" + }, + "servers": { + "description": "Servers lists MCPServer names in this group", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "set" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/master-standalone/mcpgroup-stable-v1beta1.json b/crdSchemas/master-standalone/mcpgroup-stable-v1beta1.json new file mode 100644 index 0000000..dfca32c --- /dev/null +++ b/crdSchemas/master-standalone/mcpgroup-stable-v1beta1.json @@ -0,0 +1,149 @@ +{ + "description": "MCPGroup is the Schema for the mcpgroups API", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPGroupSpec defines the desired state of MCPGroup", + "properties": { + "description": { + "description": "Description provides human-readable context", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "status": { + "description": "MCPGroupStatus defines observed state", + "properties": { + "conditions": { + "description": "Conditions represent observations", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "entries": { + "description": "Entries lists MCPServerEntry names in this group", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "set" + }, + "entryCount": { + "description": "EntryCount is the number of MCPServerEntries", + "format": "int32", + "type": "integer" + }, + "observedGeneration": { + "description": "ObservedGeneration reflects the generation most recently observed by the controller", + "format": "int64", + "type": "integer" + }, + "phase": { + "default": "Pending", + "description": "Phase indicates current state", + "enum": [ + "Ready", + "Pending", + "Failed" + ], + "type": "string" + }, + "remoteProxies": { + "description": "RemoteProxies lists MCPRemoteProxy names in this group", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "set" + }, + "remoteProxyCount": { + "description": "RemoteProxyCount is the number of MCPRemoteProxies", + "format": "int32", + "type": "integer" + }, + "serverCount": { + "description": "ServerCount is the number of MCPServers", + "format": "int32", + "type": "integer" + }, + "servers": { + "description": "Servers lists MCPServer names in this group", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "set" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/master-standalone/mcpoidcconfig-stable-v1alpha1.json b/crdSchemas/master-standalone/mcpoidcconfig-stable-v1alpha1.json new file mode 100644 index 0000000..aed96c8 --- /dev/null +++ b/crdSchemas/master-standalone/mcpoidcconfig-stable-v1alpha1.json @@ -0,0 +1,286 @@ +{ + "description": "MCPOIDCConfig is the deprecated v1alpha1 version of the MCPOIDCConfig resource.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPOIDCConfigSpec defines the desired state of MCPOIDCConfig.\nMCPOIDCConfig resources are namespace-scoped and can only be referenced by\nMCPServer resources in the same namespace.", + "properties": { + "inline": { + "description": "Inline contains direct OIDC configuration.\nOnly used when Type is \"inline\".", + "properties": { + "caBundleRef": { + "description": "CABundleRef references a ConfigMap containing the CA certificate bundle.\nWhen specified, ToolHive auto-mounts the ConfigMap and auto-computes ThvCABundlePath.", + "properties": { + "configMapRef": { + "description": "ConfigMapRef references a ConfigMap containing the CA certificate bundle.\nIf Key is not specified, it defaults to \"ca.crt\".", + "properties": { + "key": { + "description": "The key to select.", + "type": "string" + }, + "name": { + "default": "", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + }, + "optional": { + "description": "Specify whether the ConfigMap or its key must be defined", + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "clientId": { + "description": "ClientID is the OIDC client ID", + "type": "string" + }, + "clientSecretRef": { + "description": "ClientSecretRef is a reference to a Kubernetes Secret containing the client secret", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "insecureAllowHTTP": { + "default": false, + "description": "InsecureAllowHTTP allows HTTP (non-HTTPS) OIDC issuers for development/testing.\nWARNING: This is insecure and should NEVER be used in production.", + "type": "boolean" + }, + "introspectionUrl": { + "description": "IntrospectionURL is the URL for token introspection endpoint", + "type": "string" + }, + "issuer": { + "description": "Issuer is the OIDC issuer URL", + "type": "string" + }, + "jwksAllowPrivateIP": { + "default": false, + "description": "JWKSAllowPrivateIP allows JWKS/OIDC endpoints on private IP addresses.\nNote: at runtime, if either JWKSAllowPrivateIP or ProtectedResourceAllowPrivateIP\nis true, private IPs are allowed for all OIDC HTTP requests (JWKS, discovery, introspection).", + "type": "boolean" + }, + "jwksAuthTokenPath": { + "description": "JWKSAuthTokenPath is the path to file containing bearer token for JWKS/OIDC requests", + "type": "string" + }, + "jwksUrl": { + "description": "JWKSURL is the URL to fetch the JWKS from", + "type": "string" + }, + "protectedResourceAllowPrivateIP": { + "default": false, + "description": "ProtectedResourceAllowPrivateIP allows protected resource endpoint on private IP addresses.\nNote: at runtime, if either ProtectedResourceAllowPrivateIP or JWKSAllowPrivateIP\nis true, private IPs are allowed for all OIDC HTTP requests (JWKS, discovery, introspection).", + "type": "boolean" + } + }, + "required": [ + "issuer" + ], + "type": "object", + "additionalProperties": false + }, + "kubernetesServiceAccount": { + "description": "KubernetesServiceAccount configures OIDC for Kubernetes service account token validation.\nOnly used when Type is \"kubernetesServiceAccount\".", + "properties": { + "introspectionUrl": { + "description": "IntrospectionURL is the URL for token introspection endpoint.\nIf empty, OIDC discovery will be used to automatically determine the introspection URL.", + "type": "string" + }, + "issuer": { + "default": "https://kubernetes.default.svc", + "description": "Issuer is the OIDC issuer URL.", + "type": "string" + }, + "jwksUrl": { + "description": "JWKSURL is the URL to fetch the JWKS from.\nIf empty, OIDC discovery will be used to automatically determine the JWKS URL.", + "type": "string" + }, + "namespace": { + "description": "Namespace is the namespace of the service account.\nIf empty, uses the MCPServer's namespace.", + "type": "string" + }, + "serviceAccount": { + "description": "ServiceAccount is the name of the service account to validate tokens for.\nIf empty, uses the pod's service account.", + "type": "string" + }, + "useClusterAuth": { + "description": "UseClusterAuth enables using the Kubernetes cluster's CA bundle and service account token.\nWhen true, uses /var/run/secrets/kubernetes.io/serviceaccount/ca.crt for TLS verification\nand /var/run/secrets/kubernetes.io/serviceaccount/token for bearer token authentication.\nDefaults to true if not specified.", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type is the type of OIDC configuration source", + "enum": [ + "kubernetesServiceAccount", + "inline" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "kubernetesServiceAccount must be set when type is 'kubernetesServiceAccount', and must not be set otherwise", + "rule": "self.type == 'kubernetesServiceAccount' ? has(self.kubernetesServiceAccount) : !has(self.kubernetesServiceAccount)" + }, + { + "message": "inline must be set when type is 'inline', and must not be set otherwise", + "rule": "self.type == 'inline' ? has(self.inline) : !has(self.inline)" + } + ], + "additionalProperties": false + }, + "status": { + "description": "MCPOIDCConfigStatus defines the observed state of MCPOIDCConfig", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations of the MCPOIDCConfig's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "configHash": { + "description": "ConfigHash is a hash of the current configuration for change detection", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration is the most recent generation observed for this MCPOIDCConfig.", + "format": "int64", + "type": "integer" + }, + "referenceCount": { + "description": "ReferenceCount is the number of workloads referencing this config.", + "format": "int32", + "type": "integer" + }, + "referencingWorkloads": { + "description": "ReferencingWorkloads is a list of workload resources that reference this MCPOIDCConfig.\nEach entry identifies the workload by kind and name.", + "items": { + "description": "WorkloadReference identifies a workload that references a shared configuration resource.\nNamespace is implicit \u2014 cross-namespace references are not supported.", + "properties": { + "kind": { + "description": "Kind is the type of workload resource", + "enum": [ + "MCPServer", + "VirtualMCPServer", + "MCPRemoteProxy" + ], + "type": "string" + }, + "name": { + "description": "Name is the name of the workload resource", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/master-standalone/mcpoidcconfig-stable-v1beta1.json b/crdSchemas/master-standalone/mcpoidcconfig-stable-v1beta1.json new file mode 100644 index 0000000..7ecc507 --- /dev/null +++ b/crdSchemas/master-standalone/mcpoidcconfig-stable-v1beta1.json @@ -0,0 +1,286 @@ +{ + "description": "MCPOIDCConfig is the Schema for the mcpoidcconfigs API.\nMCPOIDCConfig resources are namespace-scoped and can only be referenced by\nMCPServer resources within the same namespace. Cross-namespace references\nare not supported for security and isolation reasons.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPOIDCConfigSpec defines the desired state of MCPOIDCConfig.\nMCPOIDCConfig resources are namespace-scoped and can only be referenced by\nMCPServer resources in the same namespace.", + "properties": { + "inline": { + "description": "Inline contains direct OIDC configuration.\nOnly used when Type is \"inline\".", + "properties": { + "caBundleRef": { + "description": "CABundleRef references a ConfigMap containing the CA certificate bundle.\nWhen specified, ToolHive auto-mounts the ConfigMap and auto-computes ThvCABundlePath.", + "properties": { + "configMapRef": { + "description": "ConfigMapRef references a ConfigMap containing the CA certificate bundle.\nIf Key is not specified, it defaults to \"ca.crt\".", + "properties": { + "key": { + "description": "The key to select.", + "type": "string" + }, + "name": { + "default": "", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + }, + "optional": { + "description": "Specify whether the ConfigMap or its key must be defined", + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "clientId": { + "description": "ClientID is the OIDC client ID", + "type": "string" + }, + "clientSecretRef": { + "description": "ClientSecretRef is a reference to a Kubernetes Secret containing the client secret", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "insecureAllowHTTP": { + "default": false, + "description": "InsecureAllowHTTP allows HTTP (non-HTTPS) OIDC issuers for development/testing.\nWARNING: This is insecure and should NEVER be used in production.", + "type": "boolean" + }, + "introspectionUrl": { + "description": "IntrospectionURL is the URL for token introspection endpoint", + "type": "string" + }, + "issuer": { + "description": "Issuer is the OIDC issuer URL", + "type": "string" + }, + "jwksAllowPrivateIP": { + "default": false, + "description": "JWKSAllowPrivateIP allows JWKS/OIDC endpoints on private IP addresses.\nNote: at runtime, if either JWKSAllowPrivateIP or ProtectedResourceAllowPrivateIP\nis true, private IPs are allowed for all OIDC HTTP requests (JWKS, discovery, introspection).", + "type": "boolean" + }, + "jwksAuthTokenPath": { + "description": "JWKSAuthTokenPath is the path to file containing bearer token for JWKS/OIDC requests", + "type": "string" + }, + "jwksUrl": { + "description": "JWKSURL is the URL to fetch the JWKS from", + "type": "string" + }, + "protectedResourceAllowPrivateIP": { + "default": false, + "description": "ProtectedResourceAllowPrivateIP allows protected resource endpoint on private IP addresses.\nNote: at runtime, if either ProtectedResourceAllowPrivateIP or JWKSAllowPrivateIP\nis true, private IPs are allowed for all OIDC HTTP requests (JWKS, discovery, introspection).", + "type": "boolean" + } + }, + "required": [ + "issuer" + ], + "type": "object", + "additionalProperties": false + }, + "kubernetesServiceAccount": { + "description": "KubernetesServiceAccount configures OIDC for Kubernetes service account token validation.\nOnly used when Type is \"kubernetesServiceAccount\".", + "properties": { + "introspectionUrl": { + "description": "IntrospectionURL is the URL for token introspection endpoint.\nIf empty, OIDC discovery will be used to automatically determine the introspection URL.", + "type": "string" + }, + "issuer": { + "default": "https://kubernetes.default.svc", + "description": "Issuer is the OIDC issuer URL.", + "type": "string" + }, + "jwksUrl": { + "description": "JWKSURL is the URL to fetch the JWKS from.\nIf empty, OIDC discovery will be used to automatically determine the JWKS URL.", + "type": "string" + }, + "namespace": { + "description": "Namespace is the namespace of the service account.\nIf empty, uses the MCPServer's namespace.", + "type": "string" + }, + "serviceAccount": { + "description": "ServiceAccount is the name of the service account to validate tokens for.\nIf empty, uses the pod's service account.", + "type": "string" + }, + "useClusterAuth": { + "description": "UseClusterAuth enables using the Kubernetes cluster's CA bundle and service account token.\nWhen true, uses /var/run/secrets/kubernetes.io/serviceaccount/ca.crt for TLS verification\nand /var/run/secrets/kubernetes.io/serviceaccount/token for bearer token authentication.\nDefaults to true if not specified.", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type is the type of OIDC configuration source", + "enum": [ + "kubernetesServiceAccount", + "inline" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "kubernetesServiceAccount must be set when type is 'kubernetesServiceAccount', and must not be set otherwise", + "rule": "self.type == 'kubernetesServiceAccount' ? has(self.kubernetesServiceAccount) : !has(self.kubernetesServiceAccount)" + }, + { + "message": "inline must be set when type is 'inline', and must not be set otherwise", + "rule": "self.type == 'inline' ? has(self.inline) : !has(self.inline)" + } + ], + "additionalProperties": false + }, + "status": { + "description": "MCPOIDCConfigStatus defines the observed state of MCPOIDCConfig", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations of the MCPOIDCConfig's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "configHash": { + "description": "ConfigHash is a hash of the current configuration for change detection", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration is the most recent generation observed for this MCPOIDCConfig.", + "format": "int64", + "type": "integer" + }, + "referenceCount": { + "description": "ReferenceCount is the number of workloads referencing this config.", + "format": "int32", + "type": "integer" + }, + "referencingWorkloads": { + "description": "ReferencingWorkloads is a list of workload resources that reference this MCPOIDCConfig.\nEach entry identifies the workload by kind and name.", + "items": { + "description": "WorkloadReference identifies a workload that references a shared configuration resource.\nNamespace is implicit \u2014 cross-namespace references are not supported.", + "properties": { + "kind": { + "description": "Kind is the type of workload resource", + "enum": [ + "MCPServer", + "VirtualMCPServer", + "MCPRemoteProxy" + ], + "type": "string" + }, + "name": { + "description": "Name is the name of the workload resource", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/master-standalone/mcpregistry-stable-v1alpha1.json b/crdSchemas/master-standalone/mcpregistry-stable-v1alpha1.json new file mode 100644 index 0000000..fd95beb --- /dev/null +++ b/crdSchemas/master-standalone/mcpregistry-stable-v1alpha1.json @@ -0,0 +1,196 @@ +{ + "description": "MCPRegistry is the deprecated v1alpha1 version of the MCPRegistry resource.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPRegistrySpec defines the desired state of MCPRegistry", + "properties": { + "configYAML": { + "description": "ConfigYAML is the complete registry server config.yaml content.\nThe operator creates a ConfigMap from this string and mounts it\nat /config/config.yaml in the registry-api container.\nThe operator does NOT parse, validate, or transform this content \u2014\nconfiguration validation is the registry server's responsibility.\n\nSecurity note: this content is stored in a ConfigMap, not a Secret.\nDo not inline credentials (passwords, tokens, client secrets) in this\nfield. Instead, reference credentials via file paths and mount the\nactual secrets using the Volumes and VolumeMounts fields. For database\npasswords, use PGPassSecretRef.", + "minLength": 1, + "type": "string" + }, + "displayName": { + "description": "DisplayName is a human-readable name for the registry.", + "type": "string" + }, + "imagePullSecrets": { + "description": "ImagePullSecrets allows specifying image pull secrets for the registry API workload.\nThese are applied to both the registry-api Deployment's PodSpec.ImagePullSecrets\nand to the operator-managed ServiceAccount the registry API runs as, so private\nimages are pullable through either path.\n\nUse this field for new manifests.\n\nImportant: this is the ONLY way to attach image-pull credentials to the\noperator-managed ServiceAccount. The legacy\nspec.podTemplateSpec.spec.imagePullSecrets path populates the Deployment's pod\nspec ONLY \u2014 it does NOT touch the ServiceAccount. On managed Kubernetes\nplatforms that rely on ServiceAccount-level credential injection (for example\nGKE Workload Identity, OpenShift's per-SA dockercfg secrets, EKS IRSA), using\nonly the legacy PodTemplateSpec path can fail to pull private images even when\nthe secret exists in the namespace. Always set spec.imagePullSecrets when\nSA-level credentials matter.\n\nPrecedence with PodTemplateSpec:\n - This field is applied first as the controller-generated default.\n - Values set under spec.podTemplateSpec.spec.imagePullSecrets are user overrides\n and win on overlap. If the user supplies imagePullSecrets via PodTemplateSpec,\n those replace the default list on the Deployment (the list is treated atomically).\n - The ServiceAccount is always populated from this field \u2014 PodTemplateSpec does not\n affect the ServiceAccount.\n\nAn omitted field and an explicitly empty list are equivalent: both leave the\nServiceAccount's existing ImagePullSecrets unchanged. This preserves\nplatform-managed pull secrets (for example OpenShift's per-SA dockercfg\nentries) when overlays or patches emit an empty list. Truly clearing the\nServiceAccount's pull secrets requires recreating the resource.", + "items": { + "description": "LocalObjectReference contains enough information to let you locate the\nreferenced object inside the same namespace.", + "properties": { + "name": { + "default": "", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + } + }, + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "pgpassSecretRef": { + "description": "PGPassSecretRef references a Secret containing a pre-created pgpass file.\n\nWhy this is a dedicated field instead of a regular volume/volumeMount:\nPostgreSQL's libpq rejects pgpass files that aren't mode 0600. Kubernetes\nsecret volumes mount files as root-owned, and the registry-api container\nruns as non-root (UID 65532). A root-owned 0600 file is unreadable by\nUID 65532, and using fsGroup changes permissions to 0640 which libpq also\nrejects. The only solution is an init container that copies the file to an\nemptyDir as the app user and runs chmod 0600. This cannot be expressed\nthrough volumes/volumeMounts alone -- it requires an init container, two\nextra volumes (secret + emptyDir), a subPath mount, and an environment\nvariable, all wired together correctly.\n\nWhen specified, the operator generates all of that plumbing invisibly.\nThe user creates the Secret with pgpass-formatted content; the operator\nhandles only the Kubernetes permission mechanics.\n\nExample Secret:\n\n\tapiVersion: v1\n\tkind: Secret\n\tmetadata:\n\t name: my-pgpass\n\tstringData:\n\t .pgpass: |\n\t postgres:5432:registry:db_app:mypassword\n\t postgres:5432:registry:db_migrator:otherpassword\n\nThen reference it:\n\n\tpgpassSecretRef:\n\t name: my-pgpass\n\t key: .pgpass", + "properties": { + "key": { + "description": "The key of the secret to select from. Must be a valid secret key.", + "type": "string" + }, + "name": { + "default": "", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + }, + "optional": { + "description": "Specify whether the Secret or its key must be defined", + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + }, + "podTemplateSpec": { + "description": "PodTemplateSpec defines the pod template to use for the registry API server.\nThis allows for customizing the pod configuration beyond what is provided by the other fields.\nNote that to modify the specific container the registry API server runs in, you must specify\nthe `registry-api` container name in the PodTemplateSpec.\nThis field accepts a PodTemplateSpec object as JSON/YAML.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "volumeMounts": { + "description": "VolumeMounts defines additional volume mounts for the registry-api container.\nEach entry is a standard Kubernetes VolumeMount object (JSON/YAML).\nThe operator appends them to the container's volume mounts alongside the config mount.\n\nMount paths must match the file paths referenced in configYAML.\nFor example, if configYAML references passwordFile: /secrets/git-creds/token,\na corresponding volume mount must exist with mountPath: /secrets/git-creds.", + "items": { + "x-kubernetes-preserve-unknown-fields": true + }, + "type": "array", + "x-kubernetes-list-type": "atomic", + "x-kubernetes-preserve-unknown-fields": true + }, + "volumes": { + "description": "Volumes defines additional volumes to add to the registry API pod.\nEach entry is a standard Kubernetes Volume object (JSON/YAML).\nThe operator appends them to the pod spec alongside its own config volume.\n\nUse these to mount:\n - Secrets (git auth tokens, OAuth client secrets, CA certs)\n - ConfigMaps (registry data files)\n - PersistentVolumeClaims (registry data on persistent storage)\n - Any other volume type the registry server needs", + "items": { + "x-kubernetes-preserve-unknown-fields": true + }, + "type": "array", + "x-kubernetes-list-type": "atomic", + "x-kubernetes-preserve-unknown-fields": true + } + }, + "required": [ + "configYAML" + ], + "type": "object", + "additionalProperties": false + }, + "status": { + "description": "MCPRegistryStatus defines the observed state of MCPRegistry", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations of the MCPRegistry's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "message": { + "description": "Message provides additional information about the current phase", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration reflects the generation most recently observed by the controller", + "format": "int64", + "type": "integer" + }, + "phase": { + "description": "Phase represents the current overall phase of the MCPRegistry", + "enum": [ + "Pending", + "Ready", + "Failed", + "Terminating" + ], + "type": "string" + }, + "readyReplicas": { + "description": "ReadyReplicas is the number of ready registry API replicas", + "format": "int32", + "type": "integer" + }, + "url": { + "description": "URL is the URL where the registry API can be accessed", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/master-standalone/mcpregistry-stable-v1beta1.json b/crdSchemas/master-standalone/mcpregistry-stable-v1beta1.json new file mode 100644 index 0000000..4e343fa --- /dev/null +++ b/crdSchemas/master-standalone/mcpregistry-stable-v1beta1.json @@ -0,0 +1,196 @@ +{ + "description": "MCPRegistry is the Schema for the mcpregistries API", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPRegistrySpec defines the desired state of MCPRegistry", + "properties": { + "configYAML": { + "description": "ConfigYAML is the complete registry server config.yaml content.\nThe operator creates a ConfigMap from this string and mounts it\nat /config/config.yaml in the registry-api container.\nThe operator does NOT parse, validate, or transform this content \u2014\nconfiguration validation is the registry server's responsibility.\n\nSecurity note: this content is stored in a ConfigMap, not a Secret.\nDo not inline credentials (passwords, tokens, client secrets) in this\nfield. Instead, reference credentials via file paths and mount the\nactual secrets using the Volumes and VolumeMounts fields. For database\npasswords, use PGPassSecretRef.", + "minLength": 1, + "type": "string" + }, + "displayName": { + "description": "DisplayName is a human-readable name for the registry.", + "type": "string" + }, + "imagePullSecrets": { + "description": "ImagePullSecrets allows specifying image pull secrets for the registry API workload.\nThese are applied to both the registry-api Deployment's PodSpec.ImagePullSecrets\nand to the operator-managed ServiceAccount the registry API runs as, so private\nimages are pullable through either path.\n\nUse this field for new manifests.\n\nImportant: this is the ONLY way to attach image-pull credentials to the\noperator-managed ServiceAccount. The legacy\nspec.podTemplateSpec.spec.imagePullSecrets path populates the Deployment's pod\nspec ONLY \u2014 it does NOT touch the ServiceAccount. On managed Kubernetes\nplatforms that rely on ServiceAccount-level credential injection (for example\nGKE Workload Identity, OpenShift's per-SA dockercfg secrets, EKS IRSA), using\nonly the legacy PodTemplateSpec path can fail to pull private images even when\nthe secret exists in the namespace. Always set spec.imagePullSecrets when\nSA-level credentials matter.\n\nPrecedence with PodTemplateSpec:\n - This field is applied first as the controller-generated default.\n - Values set under spec.podTemplateSpec.spec.imagePullSecrets are user overrides\n and win on overlap. If the user supplies imagePullSecrets via PodTemplateSpec,\n those replace the default list on the Deployment (the list is treated atomically).\n - The ServiceAccount is always populated from this field \u2014 PodTemplateSpec does not\n affect the ServiceAccount.\n\nAn omitted field and an explicitly empty list are equivalent: both leave the\nServiceAccount's existing ImagePullSecrets unchanged. This preserves\nplatform-managed pull secrets (for example OpenShift's per-SA dockercfg\nentries) when overlays or patches emit an empty list. Truly clearing the\nServiceAccount's pull secrets requires recreating the resource.", + "items": { + "description": "LocalObjectReference contains enough information to let you locate the\nreferenced object inside the same namespace.", + "properties": { + "name": { + "default": "", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + } + }, + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "pgpassSecretRef": { + "description": "PGPassSecretRef references a Secret containing a pre-created pgpass file.\n\nWhy this is a dedicated field instead of a regular volume/volumeMount:\nPostgreSQL's libpq rejects pgpass files that aren't mode 0600. Kubernetes\nsecret volumes mount files as root-owned, and the registry-api container\nruns as non-root (UID 65532). A root-owned 0600 file is unreadable by\nUID 65532, and using fsGroup changes permissions to 0640 which libpq also\nrejects. The only solution is an init container that copies the file to an\nemptyDir as the app user and runs chmod 0600. This cannot be expressed\nthrough volumes/volumeMounts alone -- it requires an init container, two\nextra volumes (secret + emptyDir), a subPath mount, and an environment\nvariable, all wired together correctly.\n\nWhen specified, the operator generates all of that plumbing invisibly.\nThe user creates the Secret with pgpass-formatted content; the operator\nhandles only the Kubernetes permission mechanics.\n\nExample Secret:\n\n\tapiVersion: v1\n\tkind: Secret\n\tmetadata:\n\t name: my-pgpass\n\tstringData:\n\t .pgpass: |\n\t postgres:5432:registry:db_app:mypassword\n\t postgres:5432:registry:db_migrator:otherpassword\n\nThen reference it:\n\n\tpgpassSecretRef:\n\t name: my-pgpass\n\t key: .pgpass", + "properties": { + "key": { + "description": "The key of the secret to select from. Must be a valid secret key.", + "type": "string" + }, + "name": { + "default": "", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + }, + "optional": { + "description": "Specify whether the Secret or its key must be defined", + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + }, + "podTemplateSpec": { + "description": "PodTemplateSpec defines the pod template to use for the registry API server.\nThis allows for customizing the pod configuration beyond what is provided by the other fields.\nNote that to modify the specific container the registry API server runs in, you must specify\nthe `registry-api` container name in the PodTemplateSpec.\nThis field accepts a PodTemplateSpec object as JSON/YAML.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "volumeMounts": { + "description": "VolumeMounts defines additional volume mounts for the registry-api container.\nEach entry is a standard Kubernetes VolumeMount object (JSON/YAML).\nThe operator appends them to the container's volume mounts alongside the config mount.\n\nMount paths must match the file paths referenced in configYAML.\nFor example, if configYAML references passwordFile: /secrets/git-creds/token,\na corresponding volume mount must exist with mountPath: /secrets/git-creds.", + "items": { + "x-kubernetes-preserve-unknown-fields": true + }, + "type": "array", + "x-kubernetes-list-type": "atomic", + "x-kubernetes-preserve-unknown-fields": true + }, + "volumes": { + "description": "Volumes defines additional volumes to add to the registry API pod.\nEach entry is a standard Kubernetes Volume object (JSON/YAML).\nThe operator appends them to the pod spec alongside its own config volume.\n\nUse these to mount:\n - Secrets (git auth tokens, OAuth client secrets, CA certs)\n - ConfigMaps (registry data files)\n - PersistentVolumeClaims (registry data on persistent storage)\n - Any other volume type the registry server needs", + "items": { + "x-kubernetes-preserve-unknown-fields": true + }, + "type": "array", + "x-kubernetes-list-type": "atomic", + "x-kubernetes-preserve-unknown-fields": true + } + }, + "required": [ + "configYAML" + ], + "type": "object", + "additionalProperties": false + }, + "status": { + "description": "MCPRegistryStatus defines the observed state of MCPRegistry", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations of the MCPRegistry's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "message": { + "description": "Message provides additional information about the current phase", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration reflects the generation most recently observed by the controller", + "format": "int64", + "type": "integer" + }, + "phase": { + "description": "Phase represents the current overall phase of the MCPRegistry", + "enum": [ + "Pending", + "Ready", + "Failed", + "Terminating" + ], + "type": "string" + }, + "readyReplicas": { + "description": "ReadyReplicas is the number of ready registry API replicas", + "format": "int32", + "type": "integer" + }, + "url": { + "description": "URL is the URL where the registry API can be accessed", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/master-standalone/mcpremoteproxy-stable-v1alpha1.json b/crdSchemas/master-standalone/mcpremoteproxy-stable-v1alpha1.json new file mode 100644 index 0000000..5c4d880 --- /dev/null +++ b/crdSchemas/master-standalone/mcpremoteproxy-stable-v1alpha1.json @@ -0,0 +1,621 @@ +{ + "description": "MCPRemoteProxy is the deprecated v1alpha1 version of the MCPRemoteProxy resource.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPRemoteProxySpec defines the desired state of MCPRemoteProxy", + "properties": { + "audit": { + "description": "Audit defines audit logging configuration for the proxy", + "properties": { + "enabled": { + "default": false, + "description": "Enabled controls whether audit logging is enabled\nWhen true, enables audit logging with default configuration", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "authServerRef": { + "description": "AuthServerRef optionally references a resource that configures an embedded\nOAuth 2.0/OIDC authorization server to authenticate MCP clients.\nCurrently the only supported kind is MCPExternalAuthConfig (type: embeddedAuthServer).", + "properties": { + "kind": { + "default": "MCPExternalAuthConfig", + "description": "Kind identifies the type of the referenced resource.", + "enum": [ + "MCPExternalAuthConfig" + ], + "type": "string" + }, + "name": { + "description": "Name is the name of the referenced resource in the same namespace.", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "authzConfig": { + "description": "AuthzConfig defines authorization policy configuration for the proxy", + "properties": { + "configMap": { + "description": "ConfigMap references a ConfigMap containing authorization configuration\nOnly used when Type is \"configMap\"", + "properties": { + "key": { + "default": "authz.json", + "description": "Key is the key in the ConfigMap that contains the authorization configuration", + "type": "string" + }, + "name": { + "description": "Name is the name of the ConfigMap", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "groupClaimName": { + "description": "GroupClaimName is the JWT claim key that contains group membership for the\nprincipal. When set, takes priority over the well-known defaults\n(\"groups\", \"roles\", \"cognito:groups\"). Use this for IDPs that place\ngroups under a URI-style claim (e.g. \"https://example.com/groups\"). When\nType is \"configMap\", a group_claim_name entry in the referenced ConfigMap\nis overridden by this field if both are set.", + "maxLength": 253, + "type": "string" + }, + "groupEntityType": { + "description": "GroupEntityType is the Cedar entity type name used for principal parent\nUIDs synthesised from JWT group/role claims. Defaults to \"THVGroup\" when\nempty. Must match the entity type used in the static entity store for\ntransitive `in` checks (e.g. `ClaimGroup \u2192 PlatformRole`) to resolve.\nNamespaced names (`Foo::Bar`) are not yet supported. When Type is\n\"configMap\", a group_entity_type entry in the referenced ConfigMap is\noverridden by this field if both are set.", + "maxLength": 63, + "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", + "type": "string" + }, + "inline": { + "description": "Inline contains direct authorization configuration\nOnly used when Type is \"inline\"", + "properties": { + "entitiesJson": { + "default": "[]", + "description": "EntitiesJSON is a JSON string representing Cedar entities. Required when\ntransitive policies (e.g. `ClaimGroup \u2192 PlatformRole`) need a static\nentity store; defaults to \"[]\".", + "type": "string" + }, + "policies": { + "description": "Policies is a list of Cedar policy strings", + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "primaryUpstreamProvider": { + "description": "PrimaryUpstreamProvider names the upstream IDP whose access token's\nclaims Cedar should evaluate.\n\nDeprecated: on VirtualMCPServer this field has moved to\nspec.authServerConfig.primaryUpstreamProvider. The old location is\nstill read for one release for backward compatibility; the\nVirtualMCPServer controller emits an AuthzPrimaryUpstreamProviderDeprecated\nWarning event whenever it is consumed, and removal is planned for the\nrelease after the deprecation cycle.\n\nOn MCPServer and MCPRemoteProxy this field has always been a structural\nno-op (those CRDs do not run an embedded auth server). Setting it\ncontinues to surface the AuthzPrimaryUpstreamProviderIgnored advisory\ncondition; the deprecation does not change that behaviour.", + "maxLength": 63, + "minLength": 1, + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "type": "string" + } + }, + "required": [ + "policies" + ], + "type": "object", + "additionalProperties": false + }, + "roleClaimName": { + "description": "RoleClaimName is the JWT claim key that contains role membership for the\nprincipal. When set, the claim is extracted separately from GroupClaimName\nand both are mapped to the configured GroupEntityType. When Type is\n\"configMap\", a role_claim_name entry in the referenced ConfigMap is\noverridden by this field if both are set.", + "maxLength": 253, + "type": "string" + }, + "type": { + "default": "configMap", + "description": "Type is the type of authorization configuration", + "enum": [ + "configMap", + "inline" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "configMap must be set when type is 'configMap', and must not be set otherwise", + "rule": "self.type == 'configMap' ? has(self.configMap) : !has(self.configMap)" + }, + { + "message": "inline must be set when type is 'inline', and must not be set otherwise", + "rule": "self.type == 'inline' ? has(self.inline) : !has(self.inline)" + } + ], + "additionalProperties": false + }, + "endpointPrefix": { + "description": "EndpointPrefix is the path prefix to prepend to SSE endpoint URLs.\nThis is used to handle path-based ingress routing scenarios where the ingress\nstrips a path prefix before forwarding to the backend.", + "type": "string" + }, + "externalAuthConfigRef": { + "description": "ExternalAuthConfigRef references a MCPExternalAuthConfig resource for token exchange.\nWhen specified, the proxy will exchange validated incoming tokens for remote service tokens.\nThe referenced MCPExternalAuthConfig must exist in the same namespace as this MCPRemoteProxy.", + "properties": { + "name": { + "description": "Name is the name of the MCPExternalAuthConfig resource", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "groupRef": { + "description": "GroupRef references the MCPGroup this proxy belongs to.\nThe referenced MCPGroup must be in the same namespace.", + "properties": { + "name": { + "description": "Name is the name of the MCPGroup resource in the same namespace", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "headerForward": { + "description": "HeaderForward configures headers to inject into requests to the remote MCP server.\nUse this to add custom headers like X-Tenant-ID or correlation IDs.", + "properties": { + "addHeadersFromSecret": { + "description": "AddHeadersFromSecret references Kubernetes Secrets for sensitive header values.", + "items": { + "description": "HeaderFromSecret defines a header whose value comes from a Kubernetes Secret.", + "properties": { + "headerName": { + "description": "HeaderName is the HTTP header name (e.g., \"X-API-Key\")", + "maxLength": 255, + "minLength": 1, + "type": "string" + }, + "valueSecretRef": { + "description": "ValueSecretRef references the Secret and key containing the header value", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "headerName", + "valueSecretRef" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "headerName" + ], + "x-kubernetes-list-type": "map" + }, + "addPlaintextHeaders": { + "additionalProperties": { + "type": "string" + }, + "description": "AddPlaintextHeaders is a map of header names to literal values to inject into requests.\nWARNING: Values are stored in plaintext and visible via kubectl commands.\nUse addHeadersFromSecret for sensitive data like API keys or tokens.", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + }, + "oidcConfigRef": { + "description": "OIDCConfigRef references a shared MCPOIDCConfig resource for OIDC authentication.\nThe referenced MCPOIDCConfig must exist in the same namespace as this MCPRemoteProxy.\nPer-server overrides (audience, scopes) are specified here; shared provider config\nlives in the MCPOIDCConfig resource.", + "properties": { + "audience": { + "description": "Audience is the expected audience for token validation.\nThis MUST be unique per server to prevent token replay attacks.", + "minLength": 1, + "type": "string" + }, + "name": { + "description": "Name is the name of the MCPOIDCConfig resource", + "minLength": 1, + "type": "string" + }, + "resourceUrl": { + "description": "ResourceURL is the public URL for OAuth protected resource metadata (RFC 9728).\nWhen the server is exposed via Ingress or gateway, set this to the external\nURL that MCP clients connect to. If not specified, defaults to the internal\nKubernetes service URL.", + "type": "string" + }, + "scopes": { + "description": "Scopes is the list of OAuth scopes to advertise in the well-known endpoint (RFC 9728).\nIf empty, defaults to [\"openid\"].", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "required": [ + "audience", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "proxyPort": { + "default": 8080, + "description": "ProxyPort is the port to expose the MCP proxy on", + "format": "int32", + "maximum": 65535, + "minimum": 1, + "type": "integer" + }, + "remoteUrl": { + "description": "RemoteURL is the URL of the remote MCP server to proxy", + "pattern": "^https?://", + "type": "string" + }, + "resourceOverrides": { + "description": "ResourceOverrides allows overriding annotations and labels for resources created by the operator", + "properties": { + "proxyDeployment": { + "description": "ProxyDeployment defines overrides for the Proxy Deployment resource (toolhive proxy)", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "env": { + "description": "Env are environment variables to set in the proxy container (thv run process)\nThese affect the toolhive proxy itself, not the MCP server it manages\nUse TOOLHIVE_DEBUG=true to enable debug logging in the proxy", + "items": { + "description": "EnvVar represents an environment variable in a container", + "properties": { + "name": { + "description": "Name of the environment variable", + "type": "string" + }, + "value": { + "description": "Value of the environment variable", + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + }, + "imagePullSecrets": { + "description": "ImagePullSecrets allows specifying image pull secrets for the proxy runner\nThese are applied to both the Deployment and the ServiceAccount", + "items": { + "description": "LocalObjectReference contains enough information to let you locate the\nreferenced object inside the same namespace.", + "properties": { + "name": { + "default": "", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + } + }, + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + }, + "podTemplateMetadataOverrides": { + "description": "ResourceMetadataOverrides defines metadata overrides for a resource", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "proxyService": { + "description": "ProxyService defines overrides for the Proxy Service resource (points to the proxy deployment)", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "resources": { + "description": "Resources defines the resource requirements for the proxy container", + "properties": { + "limits": { + "description": "Limits describes the maximum amount of compute resources allowed", + "properties": { + "cpu": { + "description": "CPU is the CPU limit in cores (e.g., \"500m\" for 0.5 cores)", + "type": "string" + }, + "memory": { + "description": "Memory is the memory limit in bytes (e.g., \"64Mi\" for 64 megabytes)", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "requests": { + "description": "Requests describes the minimum amount of compute resources required", + "properties": { + "cpu": { + "description": "CPU is the CPU limit in cores (e.g., \"500m\" for 0.5 cores)", + "type": "string" + }, + "memory": { + "description": "Memory is the memory limit in bytes (e.g., \"64Mi\" for 64 megabytes)", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "serviceAccount": { + "description": "ServiceAccount is the name of an already existing service account to use by the proxy.\nIf not specified, a ServiceAccount will be created automatically and used by the proxy.", + "type": "string" + }, + "sessionAffinity": { + "default": "ClientIP", + "description": "SessionAffinity controls whether the Service routes repeated client connections to the same pod.\nMCP protocols (SSE, streamable-http) are stateful, so ClientIP is the default.\nSet to \"None\" for stateless servers or when using an external load balancer with its own affinity.", + "enum": [ + "ClientIP", + "None" + ], + "type": "string" + }, + "telemetryConfigRef": { + "description": "TelemetryConfigRef references an MCPTelemetryConfig resource for shared telemetry configuration.\nThe referenced MCPTelemetryConfig must exist in the same namespace as this MCPRemoteProxy.\nCross-namespace references are not supported for security and isolation reasons.", + "properties": { + "name": { + "description": "Name is the name of the MCPTelemetryConfig resource", + "minLength": 1, + "type": "string" + }, + "serviceName": { + "description": "ServiceName overrides the telemetry service name for this specific server.\nThis MUST be unique per server for proper observability (e.g., distinguishing\ntraces and metrics from different servers sharing the same collector).\nIf empty, defaults to the server name with \"thv-\" prefix at runtime.", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "toolConfigRef": { + "description": "ToolConfigRef references a MCPToolConfig resource for tool filtering and renaming.\nThe referenced MCPToolConfig must exist in the same namespace as this MCPRemoteProxy.\nCross-namespace references are not supported for security and isolation reasons.\nIf specified, this allows filtering and overriding tools from the remote MCP server.", + "properties": { + "name": { + "description": "Name is the name of the MCPToolConfig resource in the same namespace", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "transport": { + "default": "streamable-http", + "description": "Transport is the transport method for the remote proxy (sse or streamable-http)", + "enum": [ + "sse", + "streamable-http" + ], + "type": "string" + }, + "trustProxyHeaders": { + "default": false, + "description": "TrustProxyHeaders indicates whether to trust X-Forwarded-* headers from reverse proxies\nWhen enabled, the proxy will use X-Forwarded-Proto, X-Forwarded-Host, X-Forwarded-Port,\nand X-Forwarded-Prefix headers to construct endpoint URLs", + "type": "boolean" + } + }, + "required": [ + "remoteUrl" + ], + "type": "object", + "additionalProperties": false + }, + "status": { + "description": "MCPRemoteProxyStatus defines the observed state of MCPRemoteProxy", + "properties": { + "authServerConfigHash": { + "description": "AuthServerConfigHash is the hash of the referenced authServerRef spec,\nused to detect configuration changes and trigger reconciliation.", + "type": "string" + }, + "conditions": { + "description": "Conditions represent the latest available observations of the MCPRemoteProxy's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "externalAuthConfigHash": { + "description": "ExternalAuthConfigHash is the hash of the referenced MCPExternalAuthConfig spec", + "type": "string" + }, + "externalUrl": { + "description": "ExternalURL is the external URL where the proxy can be accessed (if exposed externally)", + "type": "string" + }, + "message": { + "description": "Message provides additional information about the current phase", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration reflects the generation of the most recently observed MCPRemoteProxy", + "format": "int64", + "type": "integer" + }, + "oidcConfigHash": { + "description": "OIDCConfigHash is the hash of the referenced MCPOIDCConfig spec for change detection", + "type": "string" + }, + "phase": { + "description": "Phase is the current phase of the MCPRemoteProxy", + "enum": [ + "Pending", + "Ready", + "Failed", + "Terminating" + ], + "type": "string" + }, + "telemetryConfigHash": { + "description": "TelemetryConfigHash stores the hash of the referenced MCPTelemetryConfig for change detection", + "type": "string" + }, + "toolConfigHash": { + "description": "ToolConfigHash stores the hash of the referenced ToolConfig for change detection", + "type": "string" + }, + "url": { + "description": "URL is the internal cluster URL where the proxy can be accessed", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/master-standalone/mcpremoteproxy-stable-v1beta1.json b/crdSchemas/master-standalone/mcpremoteproxy-stable-v1beta1.json new file mode 100644 index 0000000..1ed29ab --- /dev/null +++ b/crdSchemas/master-standalone/mcpremoteproxy-stable-v1beta1.json @@ -0,0 +1,621 @@ +{ + "description": "MCPRemoteProxy is the Schema for the mcpremoteproxies API\nIt enables proxying remote MCP servers with authentication, authorization, audit logging, and tool filtering", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPRemoteProxySpec defines the desired state of MCPRemoteProxy", + "properties": { + "audit": { + "description": "Audit defines audit logging configuration for the proxy", + "properties": { + "enabled": { + "default": false, + "description": "Enabled controls whether audit logging is enabled\nWhen true, enables audit logging with default configuration", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "authServerRef": { + "description": "AuthServerRef optionally references a resource that configures an embedded\nOAuth 2.0/OIDC authorization server to authenticate MCP clients.\nCurrently the only supported kind is MCPExternalAuthConfig (type: embeddedAuthServer).", + "properties": { + "kind": { + "default": "MCPExternalAuthConfig", + "description": "Kind identifies the type of the referenced resource.", + "enum": [ + "MCPExternalAuthConfig" + ], + "type": "string" + }, + "name": { + "description": "Name is the name of the referenced resource in the same namespace.", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "authzConfig": { + "description": "AuthzConfig defines authorization policy configuration for the proxy", + "properties": { + "configMap": { + "description": "ConfigMap references a ConfigMap containing authorization configuration\nOnly used when Type is \"configMap\"", + "properties": { + "key": { + "default": "authz.json", + "description": "Key is the key in the ConfigMap that contains the authorization configuration", + "type": "string" + }, + "name": { + "description": "Name is the name of the ConfigMap", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "groupClaimName": { + "description": "GroupClaimName is the JWT claim key that contains group membership for the\nprincipal. When set, takes priority over the well-known defaults\n(\"groups\", \"roles\", \"cognito:groups\"). Use this for IDPs that place\ngroups under a URI-style claim (e.g. \"https://example.com/groups\"). When\nType is \"configMap\", a group_claim_name entry in the referenced ConfigMap\nis overridden by this field if both are set.", + "maxLength": 253, + "type": "string" + }, + "groupEntityType": { + "description": "GroupEntityType is the Cedar entity type name used for principal parent\nUIDs synthesised from JWT group/role claims. Defaults to \"THVGroup\" when\nempty. Must match the entity type used in the static entity store for\ntransitive `in` checks (e.g. `ClaimGroup \u2192 PlatformRole`) to resolve.\nNamespaced names (`Foo::Bar`) are not yet supported. When Type is\n\"configMap\", a group_entity_type entry in the referenced ConfigMap is\noverridden by this field if both are set.", + "maxLength": 63, + "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", + "type": "string" + }, + "inline": { + "description": "Inline contains direct authorization configuration\nOnly used when Type is \"inline\"", + "properties": { + "entitiesJson": { + "default": "[]", + "description": "EntitiesJSON is a JSON string representing Cedar entities. Required when\ntransitive policies (e.g. `ClaimGroup \u2192 PlatformRole`) need a static\nentity store; defaults to \"[]\".", + "type": "string" + }, + "policies": { + "description": "Policies is a list of Cedar policy strings", + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "primaryUpstreamProvider": { + "description": "PrimaryUpstreamProvider names the upstream IDP whose access token's\nclaims Cedar should evaluate.\n\nDeprecated: on VirtualMCPServer this field has moved to\nspec.authServerConfig.primaryUpstreamProvider. The old location is\nstill read for one release for backward compatibility; the\nVirtualMCPServer controller emits an AuthzPrimaryUpstreamProviderDeprecated\nWarning event whenever it is consumed, and removal is planned for the\nrelease after the deprecation cycle.\n\nOn MCPServer and MCPRemoteProxy this field has always been a structural\nno-op (those CRDs do not run an embedded auth server). Setting it\ncontinues to surface the AuthzPrimaryUpstreamProviderIgnored advisory\ncondition; the deprecation does not change that behaviour.", + "maxLength": 63, + "minLength": 1, + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "type": "string" + } + }, + "required": [ + "policies" + ], + "type": "object", + "additionalProperties": false + }, + "roleClaimName": { + "description": "RoleClaimName is the JWT claim key that contains role membership for the\nprincipal. When set, the claim is extracted separately from GroupClaimName\nand both are mapped to the configured GroupEntityType. When Type is\n\"configMap\", a role_claim_name entry in the referenced ConfigMap is\noverridden by this field if both are set.", + "maxLength": 253, + "type": "string" + }, + "type": { + "default": "configMap", + "description": "Type is the type of authorization configuration", + "enum": [ + "configMap", + "inline" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "configMap must be set when type is 'configMap', and must not be set otherwise", + "rule": "self.type == 'configMap' ? has(self.configMap) : !has(self.configMap)" + }, + { + "message": "inline must be set when type is 'inline', and must not be set otherwise", + "rule": "self.type == 'inline' ? has(self.inline) : !has(self.inline)" + } + ], + "additionalProperties": false + }, + "endpointPrefix": { + "description": "EndpointPrefix is the path prefix to prepend to SSE endpoint URLs.\nThis is used to handle path-based ingress routing scenarios where the ingress\nstrips a path prefix before forwarding to the backend.", + "type": "string" + }, + "externalAuthConfigRef": { + "description": "ExternalAuthConfigRef references a MCPExternalAuthConfig resource for token exchange.\nWhen specified, the proxy will exchange validated incoming tokens for remote service tokens.\nThe referenced MCPExternalAuthConfig must exist in the same namespace as this MCPRemoteProxy.", + "properties": { + "name": { + "description": "Name is the name of the MCPExternalAuthConfig resource", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "groupRef": { + "description": "GroupRef references the MCPGroup this proxy belongs to.\nThe referenced MCPGroup must be in the same namespace.", + "properties": { + "name": { + "description": "Name is the name of the MCPGroup resource in the same namespace", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "headerForward": { + "description": "HeaderForward configures headers to inject into requests to the remote MCP server.\nUse this to add custom headers like X-Tenant-ID or correlation IDs.", + "properties": { + "addHeadersFromSecret": { + "description": "AddHeadersFromSecret references Kubernetes Secrets for sensitive header values.", + "items": { + "description": "HeaderFromSecret defines a header whose value comes from a Kubernetes Secret.", + "properties": { + "headerName": { + "description": "HeaderName is the HTTP header name (e.g., \"X-API-Key\")", + "maxLength": 255, + "minLength": 1, + "type": "string" + }, + "valueSecretRef": { + "description": "ValueSecretRef references the Secret and key containing the header value", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "headerName", + "valueSecretRef" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "headerName" + ], + "x-kubernetes-list-type": "map" + }, + "addPlaintextHeaders": { + "additionalProperties": { + "type": "string" + }, + "description": "AddPlaintextHeaders is a map of header names to literal values to inject into requests.\nWARNING: Values are stored in plaintext and visible via kubectl commands.\nUse addHeadersFromSecret for sensitive data like API keys or tokens.", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + }, + "oidcConfigRef": { + "description": "OIDCConfigRef references a shared MCPOIDCConfig resource for OIDC authentication.\nThe referenced MCPOIDCConfig must exist in the same namespace as this MCPRemoteProxy.\nPer-server overrides (audience, scopes) are specified here; shared provider config\nlives in the MCPOIDCConfig resource.", + "properties": { + "audience": { + "description": "Audience is the expected audience for token validation.\nThis MUST be unique per server to prevent token replay attacks.", + "minLength": 1, + "type": "string" + }, + "name": { + "description": "Name is the name of the MCPOIDCConfig resource", + "minLength": 1, + "type": "string" + }, + "resourceUrl": { + "description": "ResourceURL is the public URL for OAuth protected resource metadata (RFC 9728).\nWhen the server is exposed via Ingress or gateway, set this to the external\nURL that MCP clients connect to. If not specified, defaults to the internal\nKubernetes service URL.", + "type": "string" + }, + "scopes": { + "description": "Scopes is the list of OAuth scopes to advertise in the well-known endpoint (RFC 9728).\nIf empty, defaults to [\"openid\"].", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "required": [ + "audience", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "proxyPort": { + "default": 8080, + "description": "ProxyPort is the port to expose the MCP proxy on", + "format": "int32", + "maximum": 65535, + "minimum": 1, + "type": "integer" + }, + "remoteUrl": { + "description": "RemoteURL is the URL of the remote MCP server to proxy", + "pattern": "^https?://", + "type": "string" + }, + "resourceOverrides": { + "description": "ResourceOverrides allows overriding annotations and labels for resources created by the operator", + "properties": { + "proxyDeployment": { + "description": "ProxyDeployment defines overrides for the Proxy Deployment resource (toolhive proxy)", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "env": { + "description": "Env are environment variables to set in the proxy container (thv run process)\nThese affect the toolhive proxy itself, not the MCP server it manages\nUse TOOLHIVE_DEBUG=true to enable debug logging in the proxy", + "items": { + "description": "EnvVar represents an environment variable in a container", + "properties": { + "name": { + "description": "Name of the environment variable", + "type": "string" + }, + "value": { + "description": "Value of the environment variable", + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + }, + "imagePullSecrets": { + "description": "ImagePullSecrets allows specifying image pull secrets for the proxy runner\nThese are applied to both the Deployment and the ServiceAccount", + "items": { + "description": "LocalObjectReference contains enough information to let you locate the\nreferenced object inside the same namespace.", + "properties": { + "name": { + "default": "", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + } + }, + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + }, + "podTemplateMetadataOverrides": { + "description": "ResourceMetadataOverrides defines metadata overrides for a resource", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "proxyService": { + "description": "ProxyService defines overrides for the Proxy Service resource (points to the proxy deployment)", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "resources": { + "description": "Resources defines the resource requirements for the proxy container", + "properties": { + "limits": { + "description": "Limits describes the maximum amount of compute resources allowed", + "properties": { + "cpu": { + "description": "CPU is the CPU limit in cores (e.g., \"500m\" for 0.5 cores)", + "type": "string" + }, + "memory": { + "description": "Memory is the memory limit in bytes (e.g., \"64Mi\" for 64 megabytes)", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "requests": { + "description": "Requests describes the minimum amount of compute resources required", + "properties": { + "cpu": { + "description": "CPU is the CPU limit in cores (e.g., \"500m\" for 0.5 cores)", + "type": "string" + }, + "memory": { + "description": "Memory is the memory limit in bytes (e.g., \"64Mi\" for 64 megabytes)", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "serviceAccount": { + "description": "ServiceAccount is the name of an already existing service account to use by the proxy.\nIf not specified, a ServiceAccount will be created automatically and used by the proxy.", + "type": "string" + }, + "sessionAffinity": { + "default": "ClientIP", + "description": "SessionAffinity controls whether the Service routes repeated client connections to the same pod.\nMCP protocols (SSE, streamable-http) are stateful, so ClientIP is the default.\nSet to \"None\" for stateless servers or when using an external load balancer with its own affinity.", + "enum": [ + "ClientIP", + "None" + ], + "type": "string" + }, + "telemetryConfigRef": { + "description": "TelemetryConfigRef references an MCPTelemetryConfig resource for shared telemetry configuration.\nThe referenced MCPTelemetryConfig must exist in the same namespace as this MCPRemoteProxy.\nCross-namespace references are not supported for security and isolation reasons.", + "properties": { + "name": { + "description": "Name is the name of the MCPTelemetryConfig resource", + "minLength": 1, + "type": "string" + }, + "serviceName": { + "description": "ServiceName overrides the telemetry service name for this specific server.\nThis MUST be unique per server for proper observability (e.g., distinguishing\ntraces and metrics from different servers sharing the same collector).\nIf empty, defaults to the server name with \"thv-\" prefix at runtime.", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "toolConfigRef": { + "description": "ToolConfigRef references a MCPToolConfig resource for tool filtering and renaming.\nThe referenced MCPToolConfig must exist in the same namespace as this MCPRemoteProxy.\nCross-namespace references are not supported for security and isolation reasons.\nIf specified, this allows filtering and overriding tools from the remote MCP server.", + "properties": { + "name": { + "description": "Name is the name of the MCPToolConfig resource in the same namespace", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "transport": { + "default": "streamable-http", + "description": "Transport is the transport method for the remote proxy (sse or streamable-http)", + "enum": [ + "sse", + "streamable-http" + ], + "type": "string" + }, + "trustProxyHeaders": { + "default": false, + "description": "TrustProxyHeaders indicates whether to trust X-Forwarded-* headers from reverse proxies\nWhen enabled, the proxy will use X-Forwarded-Proto, X-Forwarded-Host, X-Forwarded-Port,\nand X-Forwarded-Prefix headers to construct endpoint URLs", + "type": "boolean" + } + }, + "required": [ + "remoteUrl" + ], + "type": "object", + "additionalProperties": false + }, + "status": { + "description": "MCPRemoteProxyStatus defines the observed state of MCPRemoteProxy", + "properties": { + "authServerConfigHash": { + "description": "AuthServerConfigHash is the hash of the referenced authServerRef spec,\nused to detect configuration changes and trigger reconciliation.", + "type": "string" + }, + "conditions": { + "description": "Conditions represent the latest available observations of the MCPRemoteProxy's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "externalAuthConfigHash": { + "description": "ExternalAuthConfigHash is the hash of the referenced MCPExternalAuthConfig spec", + "type": "string" + }, + "externalUrl": { + "description": "ExternalURL is the external URL where the proxy can be accessed (if exposed externally)", + "type": "string" + }, + "message": { + "description": "Message provides additional information about the current phase", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration reflects the generation of the most recently observed MCPRemoteProxy", + "format": "int64", + "type": "integer" + }, + "oidcConfigHash": { + "description": "OIDCConfigHash is the hash of the referenced MCPOIDCConfig spec for change detection", + "type": "string" + }, + "phase": { + "description": "Phase is the current phase of the MCPRemoteProxy", + "enum": [ + "Pending", + "Ready", + "Failed", + "Terminating" + ], + "type": "string" + }, + "telemetryConfigHash": { + "description": "TelemetryConfigHash stores the hash of the referenced MCPTelemetryConfig for change detection", + "type": "string" + }, + "toolConfigHash": { + "description": "ToolConfigHash stores the hash of the referenced ToolConfig for change detection", + "type": "string" + }, + "url": { + "description": "URL is the internal cluster URL where the proxy can be accessed", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/master-standalone/mcpserver-stable-v1alpha1.json b/crdSchemas/master-standalone/mcpserver-stable-v1alpha1.json new file mode 100644 index 0000000..df715ae --- /dev/null +++ b/crdSchemas/master-standalone/mcpserver-stable-v1alpha1.json @@ -0,0 +1,946 @@ +{ + "description": "MCPServer is the deprecated v1alpha1 version of the MCPServer resource.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPServerSpec defines the desired state of MCPServer", + "properties": { + "args": { + "description": "Args are additional arguments to pass to the MCP server", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "audit": { + "description": "Audit defines audit logging configuration for the MCP server", + "properties": { + "enabled": { + "default": false, + "description": "Enabled controls whether audit logging is enabled\nWhen true, enables audit logging with default configuration", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "authServerRef": { + "description": "AuthServerRef optionally references a resource that configures an embedded\nOAuth 2.0/OIDC authorization server to authenticate MCP clients.\nCurrently the only supported kind is MCPExternalAuthConfig (type: embeddedAuthServer).", + "properties": { + "kind": { + "default": "MCPExternalAuthConfig", + "description": "Kind identifies the type of the referenced resource.", + "enum": [ + "MCPExternalAuthConfig" + ], + "type": "string" + }, + "name": { + "description": "Name is the name of the referenced resource in the same namespace.", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "authzConfig": { + "description": "AuthzConfig defines authorization policy configuration for the MCP server", + "properties": { + "configMap": { + "description": "ConfigMap references a ConfigMap containing authorization configuration\nOnly used when Type is \"configMap\"", + "properties": { + "key": { + "default": "authz.json", + "description": "Key is the key in the ConfigMap that contains the authorization configuration", + "type": "string" + }, + "name": { + "description": "Name is the name of the ConfigMap", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "groupClaimName": { + "description": "GroupClaimName is the JWT claim key that contains group membership for the\nprincipal. When set, takes priority over the well-known defaults\n(\"groups\", \"roles\", \"cognito:groups\"). Use this for IDPs that place\ngroups under a URI-style claim (e.g. \"https://example.com/groups\"). When\nType is \"configMap\", a group_claim_name entry in the referenced ConfigMap\nis overridden by this field if both are set.", + "maxLength": 253, + "type": "string" + }, + "groupEntityType": { + "description": "GroupEntityType is the Cedar entity type name used for principal parent\nUIDs synthesised from JWT group/role claims. Defaults to \"THVGroup\" when\nempty. Must match the entity type used in the static entity store for\ntransitive `in` checks (e.g. `ClaimGroup \u2192 PlatformRole`) to resolve.\nNamespaced names (`Foo::Bar`) are not yet supported. When Type is\n\"configMap\", a group_entity_type entry in the referenced ConfigMap is\noverridden by this field if both are set.", + "maxLength": 63, + "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", + "type": "string" + }, + "inline": { + "description": "Inline contains direct authorization configuration\nOnly used when Type is \"inline\"", + "properties": { + "entitiesJson": { + "default": "[]", + "description": "EntitiesJSON is a JSON string representing Cedar entities. Required when\ntransitive policies (e.g. `ClaimGroup \u2192 PlatformRole`) need a static\nentity store; defaults to \"[]\".", + "type": "string" + }, + "policies": { + "description": "Policies is a list of Cedar policy strings", + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "primaryUpstreamProvider": { + "description": "PrimaryUpstreamProvider names the upstream IDP whose access token's\nclaims Cedar should evaluate.\n\nDeprecated: on VirtualMCPServer this field has moved to\nspec.authServerConfig.primaryUpstreamProvider. The old location is\nstill read for one release for backward compatibility; the\nVirtualMCPServer controller emits an AuthzPrimaryUpstreamProviderDeprecated\nWarning event whenever it is consumed, and removal is planned for the\nrelease after the deprecation cycle.\n\nOn MCPServer and MCPRemoteProxy this field has always been a structural\nno-op (those CRDs do not run an embedded auth server). Setting it\ncontinues to surface the AuthzPrimaryUpstreamProviderIgnored advisory\ncondition; the deprecation does not change that behaviour.", + "maxLength": 63, + "minLength": 1, + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "type": "string" + } + }, + "required": [ + "policies" + ], + "type": "object", + "additionalProperties": false + }, + "roleClaimName": { + "description": "RoleClaimName is the JWT claim key that contains role membership for the\nprincipal. When set, the claim is extracted separately from GroupClaimName\nand both are mapped to the configured GroupEntityType. When Type is\n\"configMap\", a role_claim_name entry in the referenced ConfigMap is\noverridden by this field if both are set.", + "maxLength": 253, + "type": "string" + }, + "type": { + "default": "configMap", + "description": "Type is the type of authorization configuration", + "enum": [ + "configMap", + "inline" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "configMap must be set when type is 'configMap', and must not be set otherwise", + "rule": "self.type == 'configMap' ? has(self.configMap) : !has(self.configMap)" + }, + { + "message": "inline must be set when type is 'inline', and must not be set otherwise", + "rule": "self.type == 'inline' ? has(self.inline) : !has(self.inline)" + } + ], + "additionalProperties": false + }, + "backendReplicas": { + "description": "BackendReplicas is the desired number of MCP server backend pod replicas.\nThis controls the backend Deployment (the MCP server container itself),\nindependent of the proxy runner controlled by Replicas.\nWhen nil, the operator does not set Deployment.Spec.Replicas, leaving replica\nmanagement to an HPA or other external controller.", + "format": "int32", + "minimum": 0, + "type": "integer" + }, + "endpointPrefix": { + "description": "EndpointPrefix is the path prefix to prepend to SSE endpoint URLs.\nThis is used to handle path-based ingress routing scenarios where the ingress\nstrips a path prefix before forwarding to the backend.", + "type": "string" + }, + "env": { + "description": "Env are environment variables to set in the MCP server container", + "items": { + "description": "EnvVar represents an environment variable in a container", + "properties": { + "name": { + "description": "Name of the environment variable", + "type": "string" + }, + "value": { + "description": "Value of the environment variable", + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + }, + "externalAuthConfigRef": { + "description": "ExternalAuthConfigRef references a MCPExternalAuthConfig resource for external authentication.\nThe referenced MCPExternalAuthConfig must exist in the same namespace as this MCPServer.", + "properties": { + "name": { + "description": "Name is the name of the MCPExternalAuthConfig resource", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "groupRef": { + "description": "GroupRef references the MCPGroup this server belongs to.\nThe referenced MCPGroup must be in the same namespace.", + "properties": { + "name": { + "description": "Name is the name of the MCPGroup resource in the same namespace", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "image": { + "description": "Image is the container image for the MCP server", + "type": "string" + }, + "mcpPort": { + "description": "MCPPort is the port that MCP server listens to", + "format": "int32", + "maximum": 65535, + "minimum": 1, + "type": "integer" + }, + "oidcConfigRef": { + "description": "OIDCConfigRef references a shared MCPOIDCConfig resource for OIDC authentication.\nThe referenced MCPOIDCConfig must exist in the same namespace as this MCPServer.\nPer-server overrides (audience, scopes) are specified here; shared provider config\nlives in the MCPOIDCConfig resource.", + "properties": { + "audience": { + "description": "Audience is the expected audience for token validation.\nThis MUST be unique per server to prevent token replay attacks.", + "minLength": 1, + "type": "string" + }, + "name": { + "description": "Name is the name of the MCPOIDCConfig resource", + "minLength": 1, + "type": "string" + }, + "resourceUrl": { + "description": "ResourceURL is the public URL for OAuth protected resource metadata (RFC 9728).\nWhen the server is exposed via Ingress or gateway, set this to the external\nURL that MCP clients connect to. If not specified, defaults to the internal\nKubernetes service URL.", + "type": "string" + }, + "scopes": { + "description": "Scopes is the list of OAuth scopes to advertise in the well-known endpoint (RFC 9728).\nIf empty, defaults to [\"openid\"].", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "required": [ + "audience", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "permissionProfile": { + "description": "PermissionProfile defines the permission profile to use", + "properties": { + "key": { + "description": "Key is the key in the ConfigMap that contains the permission profile\nOnly used when Type is \"configmap\"", + "type": "string" + }, + "name": { + "description": "Name is the name of the permission profile\nIf Type is \"builtin\", Name must be one of: \"none\", \"network\"\nIf Type is \"configmap\", Name is the name of the ConfigMap", + "type": "string" + }, + "type": { + "default": "builtin", + "description": "Type is the type of permission profile reference", + "enum": [ + "builtin", + "configmap" + ], + "type": "string" + } + }, + "required": [ + "name", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "podTemplateSpec": { + "description": "PodTemplateSpec defines the pod template to use for the MCP server\nThis allows for customizing the pod configuration beyond what is provided by the other fields.\nNote that to modify the specific container the MCP server runs in, you must specify\nthe `mcp` container name in the PodTemplateSpec.\nThis field accepts a PodTemplateSpec object as JSON/YAML.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "proxyMode": { + "default": "streamable-http", + "description": "ProxyMode is the proxy mode for stdio transport (sse or streamable-http)\nThis setting is ONLY applicable when Transport is \"stdio\".\nFor direct transports (sse, streamable-http), this field is ignored.\nThe default value is applied by Kubernetes but will be ignored for non-stdio transports.", + "enum": [ + "sse", + "streamable-http" + ], + "type": "string" + }, + "proxyPort": { + "default": 8080, + "description": "ProxyPort is the port to expose the proxy runner on", + "format": "int32", + "maximum": 65535, + "minimum": 1, + "type": "integer" + }, + "rateLimiting": { + "description": "RateLimiting defines rate limiting configuration for the MCP server.\nRequires Redis session storage to be configured for distributed rate limiting.", + "properties": { + "perUser": { + "description": "PerUser is a token bucket applied independently to each authenticated user\nat the server level. Requires authentication to be enabled.\nEach unique userID creates Redis keys that expire after 2x refillPeriod.\nMemory formula: unique_users_per_TTL_window * (1 + num_tools_with_per_user_limits) keys.", + "properties": { + "maxTokens": { + "description": "MaxTokens is the maximum number of tokens (bucket capacity).\nThis is also the burst size: the maximum number of requests that can be served\ninstantaneously before the bucket is depleted.", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "refillPeriod": { + "description": "RefillPeriod is the duration to fully refill the bucket from zero to maxTokens.\nThe effective refill rate is maxTokens / refillPeriod tokens per second.\nFormat: Go duration string (e.g., \"1m0s\", \"30s\", \"1h0m0s\").", + "type": "string" + } + }, + "required": [ + "maxTokens", + "refillPeriod" + ], + "type": "object", + "additionalProperties": false + }, + "shared": { + "description": "Shared is a token bucket shared across all users for the entire server.", + "properties": { + "maxTokens": { + "description": "MaxTokens is the maximum number of tokens (bucket capacity).\nThis is also the burst size: the maximum number of requests that can be served\ninstantaneously before the bucket is depleted.", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "refillPeriod": { + "description": "RefillPeriod is the duration to fully refill the bucket from zero to maxTokens.\nThe effective refill rate is maxTokens / refillPeriod tokens per second.\nFormat: Go duration string (e.g., \"1m0s\", \"30s\", \"1h0m0s\").", + "type": "string" + } + }, + "required": [ + "maxTokens", + "refillPeriod" + ], + "type": "object", + "additionalProperties": false + }, + "tools": { + "description": "Tools defines per-tool rate limit overrides.\nEach entry applies additional rate limits to calls targeting a specific tool name.\nA request must pass both the server-level limit and the per-tool limit.", + "items": { + "description": "ToolRateLimitConfig defines rate limits for a specific tool.\nAt least one of shared or perUser must be configured.", + "properties": { + "name": { + "description": "Name is the MCP tool name this limit applies to.", + "minLength": 1, + "type": "string" + }, + "perUser": { + "description": "PerUser token bucket configuration for this tool.", + "properties": { + "maxTokens": { + "description": "MaxTokens is the maximum number of tokens (bucket capacity).\nThis is also the burst size: the maximum number of requests that can be served\ninstantaneously before the bucket is depleted.", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "refillPeriod": { + "description": "RefillPeriod is the duration to fully refill the bucket from zero to maxTokens.\nThe effective refill rate is maxTokens / refillPeriod tokens per second.\nFormat: Go duration string (e.g., \"1m0s\", \"30s\", \"1h0m0s\").", + "type": "string" + } + }, + "required": [ + "maxTokens", + "refillPeriod" + ], + "type": "object", + "additionalProperties": false + }, + "shared": { + "description": "Shared token bucket for this specific tool.", + "properties": { + "maxTokens": { + "description": "MaxTokens is the maximum number of tokens (bucket capacity).\nThis is also the burst size: the maximum number of requests that can be served\ninstantaneously before the bucket is depleted.", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "refillPeriod": { + "description": "RefillPeriod is the duration to fully refill the bucket from zero to maxTokens.\nThe effective refill rate is maxTokens / refillPeriod tokens per second.\nFormat: Go duration string (e.g., \"1m0s\", \"30s\", \"1h0m0s\").", + "type": "string" + } + }, + "required": [ + "maxTokens", + "refillPeriod" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "name" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "at least one of shared or perUser must be configured", + "rule": "has(self.shared) || has(self.perUser)" + } + ], + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "type": "object", + "x-kubernetes-validations": [ + { + "message": "at least one of shared, perUser, or tools must be configured", + "rule": "has(self.shared) || has(self.perUser) || (has(self.tools) && size(self.tools) > 0)" + } + ], + "additionalProperties": false + }, + "replicas": { + "description": "Replicas is the desired number of proxy runner (thv run) pod replicas.\nMCPServer creates two separate Deployments: one for the proxy runner and one\nfor the MCP server backend. This field controls the proxy runner Deployment.\nWhen nil, the operator does not set Deployment.Spec.Replicas, leaving replica\nmanagement to an HPA or other external controller.", + "format": "int32", + "minimum": 0, + "type": "integer" + }, + "resourceOverrides": { + "description": "ResourceOverrides allows overriding annotations and labels for resources created by the operator", + "properties": { + "proxyDeployment": { + "description": "ProxyDeployment defines overrides for the Proxy Deployment resource (toolhive proxy)", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "env": { + "description": "Env are environment variables to set in the proxy container (thv run process)\nThese affect the toolhive proxy itself, not the MCP server it manages\nUse TOOLHIVE_DEBUG=true to enable debug logging in the proxy", + "items": { + "description": "EnvVar represents an environment variable in a container", + "properties": { + "name": { + "description": "Name of the environment variable", + "type": "string" + }, + "value": { + "description": "Value of the environment variable", + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + }, + "imagePullSecrets": { + "description": "ImagePullSecrets allows specifying image pull secrets for the proxy runner\nThese are applied to both the Deployment and the ServiceAccount", + "items": { + "description": "LocalObjectReference contains enough information to let you locate the\nreferenced object inside the same namespace.", + "properties": { + "name": { + "default": "", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + } + }, + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + }, + "podTemplateMetadataOverrides": { + "description": "ResourceMetadataOverrides defines metadata overrides for a resource", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "proxyService": { + "description": "ProxyService defines overrides for the Proxy Service resource (points to the proxy deployment)", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "resources": { + "description": "Resources defines the resource requirements for the MCP server container", + "properties": { + "limits": { + "description": "Limits describes the maximum amount of compute resources allowed", + "properties": { + "cpu": { + "description": "CPU is the CPU limit in cores (e.g., \"500m\" for 0.5 cores)", + "type": "string" + }, + "memory": { + "description": "Memory is the memory limit in bytes (e.g., \"64Mi\" for 64 megabytes)", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "requests": { + "description": "Requests describes the minimum amount of compute resources required", + "properties": { + "cpu": { + "description": "CPU is the CPU limit in cores (e.g., \"500m\" for 0.5 cores)", + "type": "string" + }, + "memory": { + "description": "Memory is the memory limit in bytes (e.g., \"64Mi\" for 64 megabytes)", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "secrets": { + "description": "Secrets are references to secrets to mount in the MCP server container", + "items": { + "description": "SecretRef is a reference to a secret", + "properties": { + "key": { + "description": "Key is the key in the secret itself", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + }, + "targetEnvName": { + "description": "TargetEnvName is the environment variable to be used when setting up the secret in the MCP server\nIf left unspecified, it defaults to the key", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + }, + "serviceAccount": { + "description": "ServiceAccount is the name of an already existing service account to use by the MCP server.\nIf not specified, a ServiceAccount will be created automatically and used by the MCP server.", + "type": "string" + }, + "sessionAffinity": { + "default": "ClientIP", + "description": "SessionAffinity controls whether the Service routes repeated client connections to the same pod.\nMCP protocols (SSE, streamable-http) are stateful, so ClientIP is the default.\nSet to \"None\" for stateless servers or when using an external load balancer with its own affinity.", + "enum": [ + "ClientIP", + "None" + ], + "type": "string" + }, + "sessionStorage": { + "description": "SessionStorage configures session storage for stateful horizontal scaling.\nWhen nil, no session storage is configured.", + "properties": { + "address": { + "description": "Address is the Redis server address (required when provider is redis)", + "minLength": 1, + "type": "string" + }, + "db": { + "default": 0, + "description": "DB is the Redis database number", + "format": "int32", + "minimum": 0, + "type": "integer" + }, + "keyPrefix": { + "description": "KeyPrefix is an optional prefix for all Redis keys used by ToolHive", + "type": "string" + }, + "passwordRef": { + "description": "PasswordRef is a reference to a Secret key containing the Redis password", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "provider": { + "description": "Provider is the session storage backend type", + "enum": [ + "memory", + "redis" + ], + "type": "string" + } + }, + "required": [ + "provider" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "address is required", + "rule": "self.provider == 'redis' ? has(self.address) : true" + } + ], + "additionalProperties": false + }, + "telemetryConfigRef": { + "description": "TelemetryConfigRef references an MCPTelemetryConfig resource for shared telemetry configuration.\nThe referenced MCPTelemetryConfig must exist in the same namespace as this MCPServer.\nCross-namespace references are not supported for security and isolation reasons.", + "properties": { + "name": { + "description": "Name is the name of the MCPTelemetryConfig resource", + "minLength": 1, + "type": "string" + }, + "serviceName": { + "description": "ServiceName overrides the telemetry service name for this specific server.\nThis MUST be unique per server for proper observability (e.g., distinguishing\ntraces and metrics from different servers sharing the same collector).\nIf empty, defaults to the server name with \"thv-\" prefix at runtime.", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "toolConfigRef": { + "description": "ToolConfigRef references a MCPToolConfig resource for tool filtering and renaming.\nThe referenced MCPToolConfig must exist in the same namespace as this MCPServer.\nCross-namespace references are not supported for security and isolation reasons.", + "properties": { + "name": { + "description": "Name is the name of the MCPToolConfig resource in the same namespace", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "transport": { + "default": "stdio", + "description": "Transport is the transport method for the MCP server (stdio, streamable-http or sse)", + "enum": [ + "stdio", + "streamable-http", + "sse" + ], + "type": "string" + }, + "trustProxyHeaders": { + "default": false, + "description": "TrustProxyHeaders indicates whether to trust X-Forwarded-* headers from reverse proxies\nWhen enabled, the proxy will use X-Forwarded-Proto, X-Forwarded-Host, X-Forwarded-Port,\nand X-Forwarded-Prefix headers to construct endpoint URLs", + "type": "boolean" + }, + "volumes": { + "description": "Volumes are volumes to mount in the MCP server container", + "items": { + "description": "Volume represents a volume to mount in a container", + "properties": { + "hostPath": { + "description": "HostPath is the path on the host to mount", + "type": "string" + }, + "mountPath": { + "description": "MountPath is the path in the container to mount to", + "type": "string" + }, + "name": { + "description": "Name is the name of the volume", + "type": "string" + }, + "readOnly": { + "default": false, + "description": "ReadOnly specifies whether the volume should be mounted read-only", + "type": "boolean" + } + }, + "required": [ + "hostPath", + "mountPath", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + }, + "webhookConfigRef": { + "description": "WebhookConfigRef references a MCPWebhookConfig resource for webhook middleware configuration.\nThe referenced MCPWebhookConfig must exist in the same namespace as this MCPServer.", + "properties": { + "name": { + "description": "Name is the name of the MCPWebhookConfig resource", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "image" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "rateLimiting requires sessionStorage with provider 'redis'", + "rule": "!has(self.rateLimiting) || (has(self.sessionStorage) && self.sessionStorage.provider == 'redis')" + }, + { + "message": "rateLimiting.perUser requires authentication (oidcConfigRef or externalAuthConfigRef)", + "rule": "!(has(self.rateLimiting) && has(self.rateLimiting.perUser)) || has(self.oidcConfigRef) || has(self.externalAuthConfigRef)" + }, + { + "message": "per-tool perUser rate limiting requires authentication (oidcConfigRef or externalAuthConfigRef)", + "rule": "!has(self.rateLimiting) || !has(self.rateLimiting.tools) || self.rateLimiting.tools.all(t, !has(t.perUser)) || has(self.oidcConfigRef) || has(self.externalAuthConfigRef)" + } + ], + "additionalProperties": false + }, + "status": { + "description": "MCPServerStatus defines the observed state of MCPServer", + "properties": { + "authServerConfigHash": { + "description": "AuthServerConfigHash is the hash of the referenced authServerRef spec,\nused to detect configuration changes and trigger reconciliation.", + "type": "string" + }, + "conditions": { + "description": "Conditions represent the latest available observations of the MCPServer's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "externalAuthConfigHash": { + "description": "ExternalAuthConfigHash is the hash of the referenced MCPExternalAuthConfig spec", + "type": "string" + }, + "message": { + "description": "Message provides additional information about the current phase", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration reflects the generation most recently observed by the controller", + "format": "int64", + "type": "integer" + }, + "oidcConfigHash": { + "description": "OIDCConfigHash is the hash of the referenced MCPOIDCConfig spec for change detection", + "type": "string" + }, + "phase": { + "description": "Phase is the current phase of the MCPServer", + "enum": [ + "Pending", + "Ready", + "Failed", + "Terminating", + "Stopped" + ], + "type": "string" + }, + "readyReplicas": { + "description": "ReadyReplicas is the number of ready proxy replicas", + "format": "int32", + "type": "integer" + }, + "telemetryConfigHash": { + "description": "TelemetryConfigHash is the hash of the referenced MCPTelemetryConfig spec for change detection", + "type": "string" + }, + "toolConfigHash": { + "description": "ToolConfigHash stores the hash of the referenced ToolConfig for change detection", + "type": "string" + }, + "url": { + "description": "URL is the URL where the MCP server can be accessed", + "type": "string" + }, + "webhookConfigHash": { + "description": "WebhookConfigHash is the hash of the referenced MCPWebhookConfig spec", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/master-standalone/mcpserver-stable-v1beta1.json b/crdSchemas/master-standalone/mcpserver-stable-v1beta1.json new file mode 100644 index 0000000..76781b3 --- /dev/null +++ b/crdSchemas/master-standalone/mcpserver-stable-v1beta1.json @@ -0,0 +1,946 @@ +{ + "description": "MCPServer is the Schema for the mcpservers API", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPServerSpec defines the desired state of MCPServer", + "properties": { + "args": { + "description": "Args are additional arguments to pass to the MCP server", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "audit": { + "description": "Audit defines audit logging configuration for the MCP server", + "properties": { + "enabled": { + "default": false, + "description": "Enabled controls whether audit logging is enabled\nWhen true, enables audit logging with default configuration", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "authServerRef": { + "description": "AuthServerRef optionally references a resource that configures an embedded\nOAuth 2.0/OIDC authorization server to authenticate MCP clients.\nCurrently the only supported kind is MCPExternalAuthConfig (type: embeddedAuthServer).", + "properties": { + "kind": { + "default": "MCPExternalAuthConfig", + "description": "Kind identifies the type of the referenced resource.", + "enum": [ + "MCPExternalAuthConfig" + ], + "type": "string" + }, + "name": { + "description": "Name is the name of the referenced resource in the same namespace.", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "authzConfig": { + "description": "AuthzConfig defines authorization policy configuration for the MCP server", + "properties": { + "configMap": { + "description": "ConfigMap references a ConfigMap containing authorization configuration\nOnly used when Type is \"configMap\"", + "properties": { + "key": { + "default": "authz.json", + "description": "Key is the key in the ConfigMap that contains the authorization configuration", + "type": "string" + }, + "name": { + "description": "Name is the name of the ConfigMap", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "groupClaimName": { + "description": "GroupClaimName is the JWT claim key that contains group membership for the\nprincipal. When set, takes priority over the well-known defaults\n(\"groups\", \"roles\", \"cognito:groups\"). Use this for IDPs that place\ngroups under a URI-style claim (e.g. \"https://example.com/groups\"). When\nType is \"configMap\", a group_claim_name entry in the referenced ConfigMap\nis overridden by this field if both are set.", + "maxLength": 253, + "type": "string" + }, + "groupEntityType": { + "description": "GroupEntityType is the Cedar entity type name used for principal parent\nUIDs synthesised from JWT group/role claims. Defaults to \"THVGroup\" when\nempty. Must match the entity type used in the static entity store for\ntransitive `in` checks (e.g. `ClaimGroup \u2192 PlatformRole`) to resolve.\nNamespaced names (`Foo::Bar`) are not yet supported. When Type is\n\"configMap\", a group_entity_type entry in the referenced ConfigMap is\noverridden by this field if both are set.", + "maxLength": 63, + "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", + "type": "string" + }, + "inline": { + "description": "Inline contains direct authorization configuration\nOnly used when Type is \"inline\"", + "properties": { + "entitiesJson": { + "default": "[]", + "description": "EntitiesJSON is a JSON string representing Cedar entities. Required when\ntransitive policies (e.g. `ClaimGroup \u2192 PlatformRole`) need a static\nentity store; defaults to \"[]\".", + "type": "string" + }, + "policies": { + "description": "Policies is a list of Cedar policy strings", + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "primaryUpstreamProvider": { + "description": "PrimaryUpstreamProvider names the upstream IDP whose access token's\nclaims Cedar should evaluate.\n\nDeprecated: on VirtualMCPServer this field has moved to\nspec.authServerConfig.primaryUpstreamProvider. The old location is\nstill read for one release for backward compatibility; the\nVirtualMCPServer controller emits an AuthzPrimaryUpstreamProviderDeprecated\nWarning event whenever it is consumed, and removal is planned for the\nrelease after the deprecation cycle.\n\nOn MCPServer and MCPRemoteProxy this field has always been a structural\nno-op (those CRDs do not run an embedded auth server). Setting it\ncontinues to surface the AuthzPrimaryUpstreamProviderIgnored advisory\ncondition; the deprecation does not change that behaviour.", + "maxLength": 63, + "minLength": 1, + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "type": "string" + } + }, + "required": [ + "policies" + ], + "type": "object", + "additionalProperties": false + }, + "roleClaimName": { + "description": "RoleClaimName is the JWT claim key that contains role membership for the\nprincipal. When set, the claim is extracted separately from GroupClaimName\nand both are mapped to the configured GroupEntityType. When Type is\n\"configMap\", a role_claim_name entry in the referenced ConfigMap is\noverridden by this field if both are set.", + "maxLength": 253, + "type": "string" + }, + "type": { + "default": "configMap", + "description": "Type is the type of authorization configuration", + "enum": [ + "configMap", + "inline" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "configMap must be set when type is 'configMap', and must not be set otherwise", + "rule": "self.type == 'configMap' ? has(self.configMap) : !has(self.configMap)" + }, + { + "message": "inline must be set when type is 'inline', and must not be set otherwise", + "rule": "self.type == 'inline' ? has(self.inline) : !has(self.inline)" + } + ], + "additionalProperties": false + }, + "backendReplicas": { + "description": "BackendReplicas is the desired number of MCP server backend pod replicas.\nThis controls the backend Deployment (the MCP server container itself),\nindependent of the proxy runner controlled by Replicas.\nWhen nil, the operator does not set Deployment.Spec.Replicas, leaving replica\nmanagement to an HPA or other external controller.", + "format": "int32", + "minimum": 0, + "type": "integer" + }, + "endpointPrefix": { + "description": "EndpointPrefix is the path prefix to prepend to SSE endpoint URLs.\nThis is used to handle path-based ingress routing scenarios where the ingress\nstrips a path prefix before forwarding to the backend.", + "type": "string" + }, + "env": { + "description": "Env are environment variables to set in the MCP server container", + "items": { + "description": "EnvVar represents an environment variable in a container", + "properties": { + "name": { + "description": "Name of the environment variable", + "type": "string" + }, + "value": { + "description": "Value of the environment variable", + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + }, + "externalAuthConfigRef": { + "description": "ExternalAuthConfigRef references a MCPExternalAuthConfig resource for external authentication.\nThe referenced MCPExternalAuthConfig must exist in the same namespace as this MCPServer.", + "properties": { + "name": { + "description": "Name is the name of the MCPExternalAuthConfig resource", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "groupRef": { + "description": "GroupRef references the MCPGroup this server belongs to.\nThe referenced MCPGroup must be in the same namespace.", + "properties": { + "name": { + "description": "Name is the name of the MCPGroup resource in the same namespace", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "image": { + "description": "Image is the container image for the MCP server", + "type": "string" + }, + "mcpPort": { + "description": "MCPPort is the port that MCP server listens to", + "format": "int32", + "maximum": 65535, + "minimum": 1, + "type": "integer" + }, + "oidcConfigRef": { + "description": "OIDCConfigRef references a shared MCPOIDCConfig resource for OIDC authentication.\nThe referenced MCPOIDCConfig must exist in the same namespace as this MCPServer.\nPer-server overrides (audience, scopes) are specified here; shared provider config\nlives in the MCPOIDCConfig resource.", + "properties": { + "audience": { + "description": "Audience is the expected audience for token validation.\nThis MUST be unique per server to prevent token replay attacks.", + "minLength": 1, + "type": "string" + }, + "name": { + "description": "Name is the name of the MCPOIDCConfig resource", + "minLength": 1, + "type": "string" + }, + "resourceUrl": { + "description": "ResourceURL is the public URL for OAuth protected resource metadata (RFC 9728).\nWhen the server is exposed via Ingress or gateway, set this to the external\nURL that MCP clients connect to. If not specified, defaults to the internal\nKubernetes service URL.", + "type": "string" + }, + "scopes": { + "description": "Scopes is the list of OAuth scopes to advertise in the well-known endpoint (RFC 9728).\nIf empty, defaults to [\"openid\"].", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "required": [ + "audience", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "permissionProfile": { + "description": "PermissionProfile defines the permission profile to use", + "properties": { + "key": { + "description": "Key is the key in the ConfigMap that contains the permission profile\nOnly used when Type is \"configmap\"", + "type": "string" + }, + "name": { + "description": "Name is the name of the permission profile\nIf Type is \"builtin\", Name must be one of: \"none\", \"network\"\nIf Type is \"configmap\", Name is the name of the ConfigMap", + "type": "string" + }, + "type": { + "default": "builtin", + "description": "Type is the type of permission profile reference", + "enum": [ + "builtin", + "configmap" + ], + "type": "string" + } + }, + "required": [ + "name", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "podTemplateSpec": { + "description": "PodTemplateSpec defines the pod template to use for the MCP server\nThis allows for customizing the pod configuration beyond what is provided by the other fields.\nNote that to modify the specific container the MCP server runs in, you must specify\nthe `mcp` container name in the PodTemplateSpec.\nThis field accepts a PodTemplateSpec object as JSON/YAML.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "proxyMode": { + "default": "streamable-http", + "description": "ProxyMode is the proxy mode for stdio transport (sse or streamable-http)\nThis setting is ONLY applicable when Transport is \"stdio\".\nFor direct transports (sse, streamable-http), this field is ignored.\nThe default value is applied by Kubernetes but will be ignored for non-stdio transports.", + "enum": [ + "sse", + "streamable-http" + ], + "type": "string" + }, + "proxyPort": { + "default": 8080, + "description": "ProxyPort is the port to expose the proxy runner on", + "format": "int32", + "maximum": 65535, + "minimum": 1, + "type": "integer" + }, + "rateLimiting": { + "description": "RateLimiting defines rate limiting configuration for the MCP server.\nRequires Redis session storage to be configured for distributed rate limiting.", + "properties": { + "perUser": { + "description": "PerUser is a token bucket applied independently to each authenticated user\nat the server level. Requires authentication to be enabled.\nEach unique userID creates Redis keys that expire after 2x refillPeriod.\nMemory formula: unique_users_per_TTL_window * (1 + num_tools_with_per_user_limits) keys.", + "properties": { + "maxTokens": { + "description": "MaxTokens is the maximum number of tokens (bucket capacity).\nThis is also the burst size: the maximum number of requests that can be served\ninstantaneously before the bucket is depleted.", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "refillPeriod": { + "description": "RefillPeriod is the duration to fully refill the bucket from zero to maxTokens.\nThe effective refill rate is maxTokens / refillPeriod tokens per second.\nFormat: Go duration string (e.g., \"1m0s\", \"30s\", \"1h0m0s\").", + "type": "string" + } + }, + "required": [ + "maxTokens", + "refillPeriod" + ], + "type": "object", + "additionalProperties": false + }, + "shared": { + "description": "Shared is a token bucket shared across all users for the entire server.", + "properties": { + "maxTokens": { + "description": "MaxTokens is the maximum number of tokens (bucket capacity).\nThis is also the burst size: the maximum number of requests that can be served\ninstantaneously before the bucket is depleted.", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "refillPeriod": { + "description": "RefillPeriod is the duration to fully refill the bucket from zero to maxTokens.\nThe effective refill rate is maxTokens / refillPeriod tokens per second.\nFormat: Go duration string (e.g., \"1m0s\", \"30s\", \"1h0m0s\").", + "type": "string" + } + }, + "required": [ + "maxTokens", + "refillPeriod" + ], + "type": "object", + "additionalProperties": false + }, + "tools": { + "description": "Tools defines per-tool rate limit overrides.\nEach entry applies additional rate limits to calls targeting a specific tool name.\nA request must pass both the server-level limit and the per-tool limit.", + "items": { + "description": "ToolRateLimitConfig defines rate limits for a specific tool.\nAt least one of shared or perUser must be configured.", + "properties": { + "name": { + "description": "Name is the MCP tool name this limit applies to.", + "minLength": 1, + "type": "string" + }, + "perUser": { + "description": "PerUser token bucket configuration for this tool.", + "properties": { + "maxTokens": { + "description": "MaxTokens is the maximum number of tokens (bucket capacity).\nThis is also the burst size: the maximum number of requests that can be served\ninstantaneously before the bucket is depleted.", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "refillPeriod": { + "description": "RefillPeriod is the duration to fully refill the bucket from zero to maxTokens.\nThe effective refill rate is maxTokens / refillPeriod tokens per second.\nFormat: Go duration string (e.g., \"1m0s\", \"30s\", \"1h0m0s\").", + "type": "string" + } + }, + "required": [ + "maxTokens", + "refillPeriod" + ], + "type": "object", + "additionalProperties": false + }, + "shared": { + "description": "Shared token bucket for this specific tool.", + "properties": { + "maxTokens": { + "description": "MaxTokens is the maximum number of tokens (bucket capacity).\nThis is also the burst size: the maximum number of requests that can be served\ninstantaneously before the bucket is depleted.", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "refillPeriod": { + "description": "RefillPeriod is the duration to fully refill the bucket from zero to maxTokens.\nThe effective refill rate is maxTokens / refillPeriod tokens per second.\nFormat: Go duration string (e.g., \"1m0s\", \"30s\", \"1h0m0s\").", + "type": "string" + } + }, + "required": [ + "maxTokens", + "refillPeriod" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "name" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "at least one of shared or perUser must be configured", + "rule": "has(self.shared) || has(self.perUser)" + } + ], + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "type": "object", + "x-kubernetes-validations": [ + { + "message": "at least one of shared, perUser, or tools must be configured", + "rule": "has(self.shared) || has(self.perUser) || (has(self.tools) && size(self.tools) > 0)" + } + ], + "additionalProperties": false + }, + "replicas": { + "description": "Replicas is the desired number of proxy runner (thv run) pod replicas.\nMCPServer creates two separate Deployments: one for the proxy runner and one\nfor the MCP server backend. This field controls the proxy runner Deployment.\nWhen nil, the operator does not set Deployment.Spec.Replicas, leaving replica\nmanagement to an HPA or other external controller.", + "format": "int32", + "minimum": 0, + "type": "integer" + }, + "resourceOverrides": { + "description": "ResourceOverrides allows overriding annotations and labels for resources created by the operator", + "properties": { + "proxyDeployment": { + "description": "ProxyDeployment defines overrides for the Proxy Deployment resource (toolhive proxy)", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "env": { + "description": "Env are environment variables to set in the proxy container (thv run process)\nThese affect the toolhive proxy itself, not the MCP server it manages\nUse TOOLHIVE_DEBUG=true to enable debug logging in the proxy", + "items": { + "description": "EnvVar represents an environment variable in a container", + "properties": { + "name": { + "description": "Name of the environment variable", + "type": "string" + }, + "value": { + "description": "Value of the environment variable", + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + }, + "imagePullSecrets": { + "description": "ImagePullSecrets allows specifying image pull secrets for the proxy runner\nThese are applied to both the Deployment and the ServiceAccount", + "items": { + "description": "LocalObjectReference contains enough information to let you locate the\nreferenced object inside the same namespace.", + "properties": { + "name": { + "default": "", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + } + }, + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + }, + "podTemplateMetadataOverrides": { + "description": "ResourceMetadataOverrides defines metadata overrides for a resource", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "proxyService": { + "description": "ProxyService defines overrides for the Proxy Service resource (points to the proxy deployment)", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "resources": { + "description": "Resources defines the resource requirements for the MCP server container", + "properties": { + "limits": { + "description": "Limits describes the maximum amount of compute resources allowed", + "properties": { + "cpu": { + "description": "CPU is the CPU limit in cores (e.g., \"500m\" for 0.5 cores)", + "type": "string" + }, + "memory": { + "description": "Memory is the memory limit in bytes (e.g., \"64Mi\" for 64 megabytes)", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "requests": { + "description": "Requests describes the minimum amount of compute resources required", + "properties": { + "cpu": { + "description": "CPU is the CPU limit in cores (e.g., \"500m\" for 0.5 cores)", + "type": "string" + }, + "memory": { + "description": "Memory is the memory limit in bytes (e.g., \"64Mi\" for 64 megabytes)", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "secrets": { + "description": "Secrets are references to secrets to mount in the MCP server container", + "items": { + "description": "SecretRef is a reference to a secret", + "properties": { + "key": { + "description": "Key is the key in the secret itself", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + }, + "targetEnvName": { + "description": "TargetEnvName is the environment variable to be used when setting up the secret in the MCP server\nIf left unspecified, it defaults to the key", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + }, + "serviceAccount": { + "description": "ServiceAccount is the name of an already existing service account to use by the MCP server.\nIf not specified, a ServiceAccount will be created automatically and used by the MCP server.", + "type": "string" + }, + "sessionAffinity": { + "default": "ClientIP", + "description": "SessionAffinity controls whether the Service routes repeated client connections to the same pod.\nMCP protocols (SSE, streamable-http) are stateful, so ClientIP is the default.\nSet to \"None\" for stateless servers or when using an external load balancer with its own affinity.", + "enum": [ + "ClientIP", + "None" + ], + "type": "string" + }, + "sessionStorage": { + "description": "SessionStorage configures session storage for stateful horizontal scaling.\nWhen nil, no session storage is configured.", + "properties": { + "address": { + "description": "Address is the Redis server address (required when provider is redis)", + "minLength": 1, + "type": "string" + }, + "db": { + "default": 0, + "description": "DB is the Redis database number", + "format": "int32", + "minimum": 0, + "type": "integer" + }, + "keyPrefix": { + "description": "KeyPrefix is an optional prefix for all Redis keys used by ToolHive", + "type": "string" + }, + "passwordRef": { + "description": "PasswordRef is a reference to a Secret key containing the Redis password", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "provider": { + "description": "Provider is the session storage backend type", + "enum": [ + "memory", + "redis" + ], + "type": "string" + } + }, + "required": [ + "provider" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "address is required", + "rule": "self.provider == 'redis' ? has(self.address) : true" + } + ], + "additionalProperties": false + }, + "telemetryConfigRef": { + "description": "TelemetryConfigRef references an MCPTelemetryConfig resource for shared telemetry configuration.\nThe referenced MCPTelemetryConfig must exist in the same namespace as this MCPServer.\nCross-namespace references are not supported for security and isolation reasons.", + "properties": { + "name": { + "description": "Name is the name of the MCPTelemetryConfig resource", + "minLength": 1, + "type": "string" + }, + "serviceName": { + "description": "ServiceName overrides the telemetry service name for this specific server.\nThis MUST be unique per server for proper observability (e.g., distinguishing\ntraces and metrics from different servers sharing the same collector).\nIf empty, defaults to the server name with \"thv-\" prefix at runtime.", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "toolConfigRef": { + "description": "ToolConfigRef references a MCPToolConfig resource for tool filtering and renaming.\nThe referenced MCPToolConfig must exist in the same namespace as this MCPServer.\nCross-namespace references are not supported for security and isolation reasons.", + "properties": { + "name": { + "description": "Name is the name of the MCPToolConfig resource in the same namespace", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "transport": { + "default": "stdio", + "description": "Transport is the transport method for the MCP server (stdio, streamable-http or sse)", + "enum": [ + "stdio", + "streamable-http", + "sse" + ], + "type": "string" + }, + "trustProxyHeaders": { + "default": false, + "description": "TrustProxyHeaders indicates whether to trust X-Forwarded-* headers from reverse proxies\nWhen enabled, the proxy will use X-Forwarded-Proto, X-Forwarded-Host, X-Forwarded-Port,\nand X-Forwarded-Prefix headers to construct endpoint URLs", + "type": "boolean" + }, + "volumes": { + "description": "Volumes are volumes to mount in the MCP server container", + "items": { + "description": "Volume represents a volume to mount in a container", + "properties": { + "hostPath": { + "description": "HostPath is the path on the host to mount", + "type": "string" + }, + "mountPath": { + "description": "MountPath is the path in the container to mount to", + "type": "string" + }, + "name": { + "description": "Name is the name of the volume", + "type": "string" + }, + "readOnly": { + "default": false, + "description": "ReadOnly specifies whether the volume should be mounted read-only", + "type": "boolean" + } + }, + "required": [ + "hostPath", + "mountPath", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + }, + "webhookConfigRef": { + "description": "WebhookConfigRef references a MCPWebhookConfig resource for webhook middleware configuration.\nThe referenced MCPWebhookConfig must exist in the same namespace as this MCPServer.", + "properties": { + "name": { + "description": "Name is the name of the MCPWebhookConfig resource", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "image" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "rateLimiting requires sessionStorage with provider 'redis'", + "rule": "!has(self.rateLimiting) || (has(self.sessionStorage) && self.sessionStorage.provider == 'redis')" + }, + { + "message": "rateLimiting.perUser requires authentication (oidcConfigRef or externalAuthConfigRef)", + "rule": "!(has(self.rateLimiting) && has(self.rateLimiting.perUser)) || has(self.oidcConfigRef) || has(self.externalAuthConfigRef)" + }, + { + "message": "per-tool perUser rate limiting requires authentication (oidcConfigRef or externalAuthConfigRef)", + "rule": "!has(self.rateLimiting) || !has(self.rateLimiting.tools) || self.rateLimiting.tools.all(t, !has(t.perUser)) || has(self.oidcConfigRef) || has(self.externalAuthConfigRef)" + } + ], + "additionalProperties": false + }, + "status": { + "description": "MCPServerStatus defines the observed state of MCPServer", + "properties": { + "authServerConfigHash": { + "description": "AuthServerConfigHash is the hash of the referenced authServerRef spec,\nused to detect configuration changes and trigger reconciliation.", + "type": "string" + }, + "conditions": { + "description": "Conditions represent the latest available observations of the MCPServer's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "externalAuthConfigHash": { + "description": "ExternalAuthConfigHash is the hash of the referenced MCPExternalAuthConfig spec", + "type": "string" + }, + "message": { + "description": "Message provides additional information about the current phase", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration reflects the generation most recently observed by the controller", + "format": "int64", + "type": "integer" + }, + "oidcConfigHash": { + "description": "OIDCConfigHash is the hash of the referenced MCPOIDCConfig spec for change detection", + "type": "string" + }, + "phase": { + "description": "Phase is the current phase of the MCPServer", + "enum": [ + "Pending", + "Ready", + "Failed", + "Terminating", + "Stopped" + ], + "type": "string" + }, + "readyReplicas": { + "description": "ReadyReplicas is the number of ready proxy replicas", + "format": "int32", + "type": "integer" + }, + "telemetryConfigHash": { + "description": "TelemetryConfigHash is the hash of the referenced MCPTelemetryConfig spec for change detection", + "type": "string" + }, + "toolConfigHash": { + "description": "ToolConfigHash stores the hash of the referenced ToolConfig for change detection", + "type": "string" + }, + "url": { + "description": "URL is the URL where the MCP server can be accessed", + "type": "string" + }, + "webhookConfigHash": { + "description": "WebhookConfigHash is the hash of the referenced MCPWebhookConfig spec", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/master-standalone/mcpserverentry-stable-v1alpha1.json b/crdSchemas/master-standalone/mcpserverentry-stable-v1alpha1.json new file mode 100644 index 0000000..3e31b1c --- /dev/null +++ b/crdSchemas/master-standalone/mcpserverentry-stable-v1alpha1.json @@ -0,0 +1,242 @@ +{ + "description": "MCPServerEntry is the deprecated v1alpha1 version of the MCPServerEntry resource.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPServerEntrySpec defines the desired state of MCPServerEntry.\nMCPServerEntry is a zero-infrastructure catalog entry that declares a remote MCP\nserver endpoint. Unlike MCPRemoteProxy, it creates no pods, services, or deployments.", + "properties": { + "caBundleRef": { + "description": "CABundleRef references a ConfigMap containing CA certificates for TLS verification\nwhen connecting to the remote MCP server.", + "properties": { + "configMapRef": { + "description": "ConfigMapRef references a ConfigMap containing the CA certificate bundle.\nIf Key is not specified, it defaults to \"ca.crt\".", + "properties": { + "key": { + "description": "The key to select.", + "type": "string" + }, + "name": { + "default": "", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + }, + "optional": { + "description": "Specify whether the ConfigMap or its key must be defined", + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "externalAuthConfigRef": { + "description": "ExternalAuthConfigRef references a MCPExternalAuthConfig resource for token exchange\nwhen connecting to the remote MCP server. The referenced MCPExternalAuthConfig must\nexist in the same namespace as this MCPServerEntry.", + "properties": { + "name": { + "description": "Name is the name of the MCPExternalAuthConfig resource", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "groupRef": { + "description": "GroupRef references the MCPGroup this entry belongs to.\nRequired \u2014 every MCPServerEntry must be part of a group for vMCP discovery.", + "properties": { + "name": { + "description": "Name is the name of the MCPGroup resource in the same namespace", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "headerForward": { + "description": "HeaderForward configures headers to inject into requests to the remote MCP server.\nUse this to add custom headers like API keys or correlation IDs.", + "properties": { + "addHeadersFromSecret": { + "description": "AddHeadersFromSecret references Kubernetes Secrets for sensitive header values.", + "items": { + "description": "HeaderFromSecret defines a header whose value comes from a Kubernetes Secret.", + "properties": { + "headerName": { + "description": "HeaderName is the HTTP header name (e.g., \"X-API-Key\")", + "maxLength": 255, + "minLength": 1, + "type": "string" + }, + "valueSecretRef": { + "description": "ValueSecretRef references the Secret and key containing the header value", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "headerName", + "valueSecretRef" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "headerName" + ], + "x-kubernetes-list-type": "map" + }, + "addPlaintextHeaders": { + "additionalProperties": { + "type": "string" + }, + "description": "AddPlaintextHeaders is a map of header names to literal values to inject into requests.\nWARNING: Values are stored in plaintext and visible via kubectl commands.\nUse addHeadersFromSecret for sensitive data like API keys or tokens.", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + }, + "remoteUrl": { + "description": "RemoteURL is the URL of the remote MCP server.\nBoth HTTP and HTTPS schemes are accepted at admission time.", + "pattern": "^https?://", + "type": "string" + }, + "transport": { + "description": "Transport is the transport method for the remote server (sse or streamable-http).\nNo default is set (unlike MCPRemoteProxy) because MCPServerEntry points at external\nservers the user doesn't control \u2014 requiring explicit transport avoids silent mismatches.", + "enum": [ + "sse", + "streamable-http" + ], + "type": "string" + } + }, + "required": [ + "groupRef", + "remoteUrl", + "transport" + ], + "type": "object", + "additionalProperties": false + }, + "status": { + "description": "MCPServerEntryStatus defines the observed state of MCPServerEntry.", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations of the MCPServerEntry's state.", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "observedGeneration": { + "description": "ObservedGeneration reflects the generation most recently observed by the controller.", + "format": "int64", + "type": "integer" + }, + "phase": { + "default": "Pending", + "description": "Phase indicates the current lifecycle phase of the MCPServerEntry.", + "enum": [ + "Valid", + "Pending", + "Failed" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/master-standalone/mcpserverentry-stable-v1beta1.json b/crdSchemas/master-standalone/mcpserverentry-stable-v1beta1.json new file mode 100644 index 0000000..a2420df --- /dev/null +++ b/crdSchemas/master-standalone/mcpserverentry-stable-v1beta1.json @@ -0,0 +1,242 @@ +{ + "description": "MCPServerEntry is the Schema for the mcpserverentries API.\nIt declares a remote MCP server endpoint for vMCP discovery and routing\nwithout deploying any infrastructure.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPServerEntrySpec defines the desired state of MCPServerEntry.\nMCPServerEntry is a zero-infrastructure catalog entry that declares a remote MCP\nserver endpoint. Unlike MCPRemoteProxy, it creates no pods, services, or deployments.", + "properties": { + "caBundleRef": { + "description": "CABundleRef references a ConfigMap containing CA certificates for TLS verification\nwhen connecting to the remote MCP server.", + "properties": { + "configMapRef": { + "description": "ConfigMapRef references a ConfigMap containing the CA certificate bundle.\nIf Key is not specified, it defaults to \"ca.crt\".", + "properties": { + "key": { + "description": "The key to select.", + "type": "string" + }, + "name": { + "default": "", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + }, + "optional": { + "description": "Specify whether the ConfigMap or its key must be defined", + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "externalAuthConfigRef": { + "description": "ExternalAuthConfigRef references a MCPExternalAuthConfig resource for token exchange\nwhen connecting to the remote MCP server. The referenced MCPExternalAuthConfig must\nexist in the same namespace as this MCPServerEntry.", + "properties": { + "name": { + "description": "Name is the name of the MCPExternalAuthConfig resource", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "groupRef": { + "description": "GroupRef references the MCPGroup this entry belongs to.\nRequired \u2014 every MCPServerEntry must be part of a group for vMCP discovery.", + "properties": { + "name": { + "description": "Name is the name of the MCPGroup resource in the same namespace", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "headerForward": { + "description": "HeaderForward configures headers to inject into requests to the remote MCP server.\nUse this to add custom headers like API keys or correlation IDs.", + "properties": { + "addHeadersFromSecret": { + "description": "AddHeadersFromSecret references Kubernetes Secrets for sensitive header values.", + "items": { + "description": "HeaderFromSecret defines a header whose value comes from a Kubernetes Secret.", + "properties": { + "headerName": { + "description": "HeaderName is the HTTP header name (e.g., \"X-API-Key\")", + "maxLength": 255, + "minLength": 1, + "type": "string" + }, + "valueSecretRef": { + "description": "ValueSecretRef references the Secret and key containing the header value", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "headerName", + "valueSecretRef" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "headerName" + ], + "x-kubernetes-list-type": "map" + }, + "addPlaintextHeaders": { + "additionalProperties": { + "type": "string" + }, + "description": "AddPlaintextHeaders is a map of header names to literal values to inject into requests.\nWARNING: Values are stored in plaintext and visible via kubectl commands.\nUse addHeadersFromSecret for sensitive data like API keys or tokens.", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + }, + "remoteUrl": { + "description": "RemoteURL is the URL of the remote MCP server.\nBoth HTTP and HTTPS schemes are accepted at admission time.", + "pattern": "^https?://", + "type": "string" + }, + "transport": { + "description": "Transport is the transport method for the remote server (sse or streamable-http).\nNo default is set (unlike MCPRemoteProxy) because MCPServerEntry points at external\nservers the user doesn't control \u2014 requiring explicit transport avoids silent mismatches.", + "enum": [ + "sse", + "streamable-http" + ], + "type": "string" + } + }, + "required": [ + "groupRef", + "remoteUrl", + "transport" + ], + "type": "object", + "additionalProperties": false + }, + "status": { + "description": "MCPServerEntryStatus defines the observed state of MCPServerEntry.", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations of the MCPServerEntry's state.", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "observedGeneration": { + "description": "ObservedGeneration reflects the generation most recently observed by the controller.", + "format": "int64", + "type": "integer" + }, + "phase": { + "default": "Pending", + "description": "Phase indicates the current lifecycle phase of the MCPServerEntry.", + "enum": [ + "Valid", + "Pending", + "Failed" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/master-standalone/mcptelemetryconfig-stable-v1alpha1.json b/crdSchemas/master-standalone/mcptelemetryconfig-stable-v1alpha1.json new file mode 100644 index 0000000..7084b43 --- /dev/null +++ b/crdSchemas/master-standalone/mcptelemetryconfig-stable-v1alpha1.json @@ -0,0 +1,295 @@ +{ + "description": "MCPTelemetryConfig is the deprecated v1alpha1 version of the MCPTelemetryConfig resource.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPTelemetryConfigSpec defines the desired state of MCPTelemetryConfig.\nThe spec uses a nested structure with openTelemetry and prometheus sub-objects\nfor clear separation of concerns.", + "properties": { + "openTelemetry": { + "description": "OpenTelemetry defines OpenTelemetry configuration (OTLP endpoint, tracing, metrics)", + "properties": { + "caBundleRef": { + "description": "CABundleRef references a ConfigMap containing a CA certificate bundle for the OTLP endpoint.\nWhen specified, the operator mounts the ConfigMap into the proxyrunner pod and configures\nthe OTLP exporters to trust the custom CA. This is useful when the OTLP collector uses\nTLS with certificates signed by an internal or private CA.", + "properties": { + "configMapRef": { + "description": "ConfigMapRef references a ConfigMap containing the CA certificate bundle.\nIf Key is not specified, it defaults to \"ca.crt\".", + "properties": { + "key": { + "description": "The key to select.", + "type": "string" + }, + "name": { + "default": "", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + }, + "optional": { + "description": "Specify whether the ConfigMap or its key must be defined", + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "enabled": { + "default": false, + "description": "Enabled controls whether OpenTelemetry is enabled", + "type": "boolean" + }, + "endpoint": { + "description": "Endpoint is the OTLP endpoint URL for tracing and metrics", + "type": "string" + }, + "headers": { + "additionalProperties": { + "type": "string" + }, + "description": "Headers contains authentication headers for the OTLP endpoint.\nFor secret-backed credentials, use sensitiveHeaders instead.", + "type": "object" + }, + "insecure": { + "default": false, + "description": "Insecure indicates whether to use HTTP instead of HTTPS for the OTLP endpoint", + "type": "boolean" + }, + "metrics": { + "description": "Metrics defines OpenTelemetry metrics-specific configuration", + "properties": { + "enabled": { + "default": false, + "description": "Enabled controls whether OTLP metrics are sent", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "resourceAttributes": { + "additionalProperties": { + "type": "string" + }, + "description": "ResourceAttributes contains custom resource attributes to be added to all telemetry signals.\nThese become OTel resource attributes (e.g., deployment.environment, service.namespace).\nNote: service.name is intentionally excluded \u2014 it is set per-server via\nMCPTelemetryConfigReference.ServiceName.", + "type": "object" + }, + "sensitiveHeaders": { + "description": "SensitiveHeaders contains headers whose values are stored in Kubernetes Secrets.\nUse this for credential headers (e.g., API keys, bearer tokens) instead of\nembedding secrets in the headers field.", + "items": { + "description": "SensitiveHeader represents a header whose value is stored in a Kubernetes Secret.\nThis allows credential headers (e.g., API keys, bearer tokens) to be securely\nreferenced without embedding secrets inline in the MCPTelemetryConfig resource.", + "properties": { + "name": { + "description": "Name is the header name (e.g., \"Authorization\", \"X-API-Key\")", + "minLength": 1, + "type": "string" + }, + "secretKeyRef": { + "description": "SecretKeyRef is a reference to a Kubernetes Secret key containing the header value", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "name", + "secretKeyRef" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + }, + "tracing": { + "description": "Tracing defines OpenTelemetry tracing configuration", + "properties": { + "enabled": { + "default": false, + "description": "Enabled controls whether OTLP tracing is sent", + "type": "boolean" + }, + "samplingRate": { + "default": "0.05", + "description": "SamplingRate is the trace sampling rate (0.0-1.0)", + "pattern": "^(0(\\.\\d+)?|1(\\.0+)?)$", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "useLegacyAttributes": { + "default": true, + "description": "UseLegacyAttributes controls whether legacy attribute names are emitted alongside\nthe new MCP OTEL semantic convention names. Defaults to true for backward compatibility.\nThis will change to false in a future release and eventually be removed.", + "type": "boolean" + } + }, + "type": "object", + "x-kubernetes-validations": [ + { + "message": "a header name cannot appear in both headers and sensitiveHeaders", + "rule": "!has(self.headers) || !has(self.sensitiveHeaders) || self.sensitiveHeaders.all(sh, !(sh.name in self.headers))" + } + ], + "additionalProperties": false + }, + "prometheus": { + "description": "Prometheus defines Prometheus-specific configuration", + "properties": { + "enabled": { + "default": false, + "description": "Enabled controls whether Prometheus metrics endpoint is exposed", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "status": { + "description": "MCPTelemetryConfigStatus defines the observed state of MCPTelemetryConfig", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations of the MCPTelemetryConfig's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "configHash": { + "description": "ConfigHash is a hash of the current configuration for change detection", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration is the most recent generation observed for this MCPTelemetryConfig.", + "format": "int64", + "type": "integer" + }, + "referencingWorkloads": { + "description": "ReferencingWorkloads lists workloads that reference this MCPTelemetryConfig", + "items": { + "description": "WorkloadReference identifies a workload that references a shared configuration resource.\nNamespace is implicit \u2014 cross-namespace references are not supported.", + "properties": { + "kind": { + "description": "Kind is the type of workload resource", + "enum": [ + "MCPServer", + "VirtualMCPServer", + "MCPRemoteProxy" + ], + "type": "string" + }, + "name": { + "description": "Name is the name of the workload resource", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/master-standalone/mcptelemetryconfig-stable-v1beta1.json b/crdSchemas/master-standalone/mcptelemetryconfig-stable-v1beta1.json new file mode 100644 index 0000000..0eb14ec --- /dev/null +++ b/crdSchemas/master-standalone/mcptelemetryconfig-stable-v1beta1.json @@ -0,0 +1,295 @@ +{ + "description": "MCPTelemetryConfig is the Schema for the mcptelemetryconfigs API.\nMCPTelemetryConfig resources are namespace-scoped and can only be referenced by\nMCPServer resources within the same namespace. Cross-namespace references\nare not supported for security and isolation reasons.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPTelemetryConfigSpec defines the desired state of MCPTelemetryConfig.\nThe spec uses a nested structure with openTelemetry and prometheus sub-objects\nfor clear separation of concerns.", + "properties": { + "openTelemetry": { + "description": "OpenTelemetry defines OpenTelemetry configuration (OTLP endpoint, tracing, metrics)", + "properties": { + "caBundleRef": { + "description": "CABundleRef references a ConfigMap containing a CA certificate bundle for the OTLP endpoint.\nWhen specified, the operator mounts the ConfigMap into the proxyrunner pod and configures\nthe OTLP exporters to trust the custom CA. This is useful when the OTLP collector uses\nTLS with certificates signed by an internal or private CA.", + "properties": { + "configMapRef": { + "description": "ConfigMapRef references a ConfigMap containing the CA certificate bundle.\nIf Key is not specified, it defaults to \"ca.crt\".", + "properties": { + "key": { + "description": "The key to select.", + "type": "string" + }, + "name": { + "default": "", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + }, + "optional": { + "description": "Specify whether the ConfigMap or its key must be defined", + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "enabled": { + "default": false, + "description": "Enabled controls whether OpenTelemetry is enabled", + "type": "boolean" + }, + "endpoint": { + "description": "Endpoint is the OTLP endpoint URL for tracing and metrics", + "type": "string" + }, + "headers": { + "additionalProperties": { + "type": "string" + }, + "description": "Headers contains authentication headers for the OTLP endpoint.\nFor secret-backed credentials, use sensitiveHeaders instead.", + "type": "object" + }, + "insecure": { + "default": false, + "description": "Insecure indicates whether to use HTTP instead of HTTPS for the OTLP endpoint", + "type": "boolean" + }, + "metrics": { + "description": "Metrics defines OpenTelemetry metrics-specific configuration", + "properties": { + "enabled": { + "default": false, + "description": "Enabled controls whether OTLP metrics are sent", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "resourceAttributes": { + "additionalProperties": { + "type": "string" + }, + "description": "ResourceAttributes contains custom resource attributes to be added to all telemetry signals.\nThese become OTel resource attributes (e.g., deployment.environment, service.namespace).\nNote: service.name is intentionally excluded \u2014 it is set per-server via\nMCPTelemetryConfigReference.ServiceName.", + "type": "object" + }, + "sensitiveHeaders": { + "description": "SensitiveHeaders contains headers whose values are stored in Kubernetes Secrets.\nUse this for credential headers (e.g., API keys, bearer tokens) instead of\nembedding secrets in the headers field.", + "items": { + "description": "SensitiveHeader represents a header whose value is stored in a Kubernetes Secret.\nThis allows credential headers (e.g., API keys, bearer tokens) to be securely\nreferenced without embedding secrets inline in the MCPTelemetryConfig resource.", + "properties": { + "name": { + "description": "Name is the header name (e.g., \"Authorization\", \"X-API-Key\")", + "minLength": 1, + "type": "string" + }, + "secretKeyRef": { + "description": "SecretKeyRef is a reference to a Kubernetes Secret key containing the header value", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "name", + "secretKeyRef" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + }, + "tracing": { + "description": "Tracing defines OpenTelemetry tracing configuration", + "properties": { + "enabled": { + "default": false, + "description": "Enabled controls whether OTLP tracing is sent", + "type": "boolean" + }, + "samplingRate": { + "default": "0.05", + "description": "SamplingRate is the trace sampling rate (0.0-1.0)", + "pattern": "^(0(\\.\\d+)?|1(\\.0+)?)$", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "useLegacyAttributes": { + "default": true, + "description": "UseLegacyAttributes controls whether legacy attribute names are emitted alongside\nthe new MCP OTEL semantic convention names. Defaults to true for backward compatibility.\nThis will change to false in a future release and eventually be removed.", + "type": "boolean" + } + }, + "type": "object", + "x-kubernetes-validations": [ + { + "message": "a header name cannot appear in both headers and sensitiveHeaders", + "rule": "!has(self.headers) || !has(self.sensitiveHeaders) || self.sensitiveHeaders.all(sh, !(sh.name in self.headers))" + } + ], + "additionalProperties": false + }, + "prometheus": { + "description": "Prometheus defines Prometheus-specific configuration", + "properties": { + "enabled": { + "default": false, + "description": "Enabled controls whether Prometheus metrics endpoint is exposed", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "status": { + "description": "MCPTelemetryConfigStatus defines the observed state of MCPTelemetryConfig", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations of the MCPTelemetryConfig's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "configHash": { + "description": "ConfigHash is a hash of the current configuration for change detection", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration is the most recent generation observed for this MCPTelemetryConfig.", + "format": "int64", + "type": "integer" + }, + "referencingWorkloads": { + "description": "ReferencingWorkloads lists workloads that reference this MCPTelemetryConfig", + "items": { + "description": "WorkloadReference identifies a workload that references a shared configuration resource.\nNamespace is implicit \u2014 cross-namespace references are not supported.", + "properties": { + "kind": { + "description": "Kind is the type of workload resource", + "enum": [ + "MCPServer", + "VirtualMCPServer", + "MCPRemoteProxy" + ], + "type": "string" + }, + "name": { + "description": "Name is the name of the workload resource", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/master-standalone/mcptoolconfig-stable-v1alpha1.json b/crdSchemas/master-standalone/mcptoolconfig-stable-v1alpha1.json new file mode 100644 index 0000000..6996292 --- /dev/null +++ b/crdSchemas/master-standalone/mcptoolconfig-stable-v1alpha1.json @@ -0,0 +1,192 @@ +{ + "description": "MCPToolConfig is the deprecated v1alpha1 version of the MCPToolConfig resource.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPToolConfigSpec defines the desired state of MCPToolConfig.\nMCPToolConfig resources are namespace-scoped and can only be referenced by\nMCPServer resources in the same namespace.", + "properties": { + "toolsFilter": { + "description": "ToolsFilter is a list of tool names to filter (allow list).\nOnly tools in this list will be exposed by the MCP server.\nIf empty, all tools are exposed.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "set" + }, + "toolsOverride": { + "additionalProperties": { + "description": "ToolOverride represents a tool override configuration.\nBoth Name and Description can be overridden independently, but\nthey can't be both empty.", + "properties": { + "annotations": { + "description": "Annotations overrides specific tool annotation fields.\nOnly specified fields are overridden; others pass through from the backend.", + "properties": { + "destructiveHint": { + "description": "DestructiveHint overrides the destructive hint annotation.", + "type": "boolean" + }, + "idempotentHint": { + "description": "IdempotentHint overrides the idempotent hint annotation.", + "type": "boolean" + }, + "openWorldHint": { + "description": "OpenWorldHint overrides the open-world hint annotation.", + "type": "boolean" + }, + "readOnlyHint": { + "description": "ReadOnlyHint overrides the read-only hint annotation.", + "type": "boolean" + }, + "title": { + "description": "Title overrides the human-readable title annotation.", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "description": { + "description": "Description is the redefined description of the tool", + "type": "string" + }, + "name": { + "description": "Name is the redefined name of the tool", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "description": "ToolsOverride is a map from actual tool names to their overridden configuration.\nThis allows renaming tools and/or changing their descriptions.", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + }, + "status": { + "description": "MCPToolConfigStatus defines the observed state of MCPToolConfig", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations of the MCPToolConfig's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "configHash": { + "description": "ConfigHash is a hash of the current configuration for change detection", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration is the most recent generation observed for this MCPToolConfig.\nIt corresponds to the MCPToolConfig's generation, which is updated on mutation by the API Server.", + "format": "int64", + "type": "integer" + }, + "referenceCount": { + "description": "ReferenceCount is the number of workloads referencing this config.", + "format": "int32", + "type": "integer" + }, + "referencingWorkloads": { + "description": "ReferencingWorkloads is a list of workload resources that reference this MCPToolConfig.\nEach entry identifies the workload by kind and name.", + "items": { + "description": "WorkloadReference identifies a workload that references a shared configuration resource.\nNamespace is implicit \u2014 cross-namespace references are not supported.", + "properties": { + "kind": { + "description": "Kind is the type of workload resource", + "enum": [ + "MCPServer", + "VirtualMCPServer", + "MCPRemoteProxy" + ], + "type": "string" + }, + "name": { + "description": "Name is the name of the workload resource", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/master-standalone/mcptoolconfig-stable-v1beta1.json b/crdSchemas/master-standalone/mcptoolconfig-stable-v1beta1.json new file mode 100644 index 0000000..3dc278c --- /dev/null +++ b/crdSchemas/master-standalone/mcptoolconfig-stable-v1beta1.json @@ -0,0 +1,192 @@ +{ + "description": "MCPToolConfig is the Schema for the mcptoolconfigs API.\nMCPToolConfig resources are namespace-scoped and can only be referenced by\nMCPServer resources within the same namespace. Cross-namespace references\nare not supported for security and isolation reasons.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPToolConfigSpec defines the desired state of MCPToolConfig.\nMCPToolConfig resources are namespace-scoped and can only be referenced by\nMCPServer resources in the same namespace.", + "properties": { + "toolsFilter": { + "description": "ToolsFilter is a list of tool names to filter (allow list).\nOnly tools in this list will be exposed by the MCP server.\nIf empty, all tools are exposed.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "set" + }, + "toolsOverride": { + "additionalProperties": { + "description": "ToolOverride represents a tool override configuration.\nBoth Name and Description can be overridden independently, but\nthey can't be both empty.", + "properties": { + "annotations": { + "description": "Annotations overrides specific tool annotation fields.\nOnly specified fields are overridden; others pass through from the backend.", + "properties": { + "destructiveHint": { + "description": "DestructiveHint overrides the destructive hint annotation.", + "type": "boolean" + }, + "idempotentHint": { + "description": "IdempotentHint overrides the idempotent hint annotation.", + "type": "boolean" + }, + "openWorldHint": { + "description": "OpenWorldHint overrides the open-world hint annotation.", + "type": "boolean" + }, + "readOnlyHint": { + "description": "ReadOnlyHint overrides the read-only hint annotation.", + "type": "boolean" + }, + "title": { + "description": "Title overrides the human-readable title annotation.", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "description": { + "description": "Description is the redefined description of the tool", + "type": "string" + }, + "name": { + "description": "Name is the redefined name of the tool", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "description": "ToolsOverride is a map from actual tool names to their overridden configuration.\nThis allows renaming tools and/or changing their descriptions.", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + }, + "status": { + "description": "MCPToolConfigStatus defines the observed state of MCPToolConfig", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations of the MCPToolConfig's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "configHash": { + "description": "ConfigHash is a hash of the current configuration for change detection", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration is the most recent generation observed for this MCPToolConfig.\nIt corresponds to the MCPToolConfig's generation, which is updated on mutation by the API Server.", + "format": "int64", + "type": "integer" + }, + "referenceCount": { + "description": "ReferenceCount is the number of workloads referencing this config.", + "format": "int32", + "type": "integer" + }, + "referencingWorkloads": { + "description": "ReferencingWorkloads is a list of workload resources that reference this MCPToolConfig.\nEach entry identifies the workload by kind and name.", + "items": { + "description": "WorkloadReference identifies a workload that references a shared configuration resource.\nNamespace is implicit \u2014 cross-namespace references are not supported.", + "properties": { + "kind": { + "description": "Kind is the type of workload resource", + "enum": [ + "MCPServer", + "VirtualMCPServer", + "MCPRemoteProxy" + ], + "type": "string" + }, + "name": { + "description": "Name is the name of the workload resource", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/master-standalone/mcpwebhookconfig-stable-v1alpha1.json b/crdSchemas/master-standalone/mcpwebhookconfig-stable-v1alpha1.json new file mode 100644 index 0000000..61507c8 --- /dev/null +++ b/crdSchemas/master-standalone/mcpwebhookconfig-stable-v1alpha1.json @@ -0,0 +1,393 @@ +{ + "description": "MCPWebhookConfig is the Schema for the mcpwebhookconfigs API.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPWebhookConfigSpec defines the desired state of MCPWebhookConfig", + "properties": { + "mutating": { + "description": "Mutating webhooks are called to transform MCP requests before processing.", + "items": { + "description": "WebhookSpec defines the configuration for a single webhook middleware", + "properties": { + "failurePolicy": { + "default": "fail", + "description": "FailurePolicy defines how to handle errors when communicating with the webhook.\nSupported values: \"fail\", \"ignore\". Defaults to \"fail\".", + "enum": [ + "fail", + "ignore" + ], + "type": "string" + }, + "hmacSecretRef": { + "description": "HMACSecretRef references a Kubernetes Secret containing the HMAC signing key\nused to sign the webhook payload. If set, the X-Toolhive-Signature header will be injected.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "name": { + "description": "Name is a unique identifier for this webhook", + "maxLength": 63, + "minLength": 1, + "type": "string" + }, + "timeout": { + "description": "Timeout configures the maximum time to wait for the webhook to respond.\nDefaults to 10s if not specified. Maximum is 30s.", + "format": "duration", + "type": "string" + }, + "tlsConfig": { + "description": "TLSConfig contains optional TLS configuration for the webhook connection.", + "properties": { + "caSecretRef": { + "description": "CASecretRef references a Secret containing the CA certificate bundle used to verify the webhook server's certificate.\nContains a bundle of PEM-encoded X.509 certificates.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "clientCertSecretRef": { + "description": "ClientCertSecretRef references a Secret containing the client certificate for mTLS authentication.\nThe referenced key must contain a PEM-encoded client certificate.\nUse ClientKeySecretRef to provide the corresponding private key.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "clientKeySecretRef": { + "description": "ClientKeySecretRef references a Secret containing the private key for the client certificate.\nRequired when ClientCertSecretRef is set to enable mTLS.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "insecureSkipVerify": { + "description": "InsecureSkipVerify disables server certificate verification.\nWARNING: This should only be used for development/testing and not in production environments.", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "url": { + "description": "URL is the endpoint to call for this webhook. Must be an HTTP/HTTPS URL.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "name", + "url" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + }, + "validating": { + "description": "Validating webhooks are called to approve or deny MCP requests.", + "items": { + "description": "WebhookSpec defines the configuration for a single webhook middleware", + "properties": { + "failurePolicy": { + "default": "fail", + "description": "FailurePolicy defines how to handle errors when communicating with the webhook.\nSupported values: \"fail\", \"ignore\". Defaults to \"fail\".", + "enum": [ + "fail", + "ignore" + ], + "type": "string" + }, + "hmacSecretRef": { + "description": "HMACSecretRef references a Kubernetes Secret containing the HMAC signing key\nused to sign the webhook payload. If set, the X-Toolhive-Signature header will be injected.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "name": { + "description": "Name is a unique identifier for this webhook", + "maxLength": 63, + "minLength": 1, + "type": "string" + }, + "timeout": { + "description": "Timeout configures the maximum time to wait for the webhook to respond.\nDefaults to 10s if not specified. Maximum is 30s.", + "format": "duration", + "type": "string" + }, + "tlsConfig": { + "description": "TLSConfig contains optional TLS configuration for the webhook connection.", + "properties": { + "caSecretRef": { + "description": "CASecretRef references a Secret containing the CA certificate bundle used to verify the webhook server's certificate.\nContains a bundle of PEM-encoded X.509 certificates.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "clientCertSecretRef": { + "description": "ClientCertSecretRef references a Secret containing the client certificate for mTLS authentication.\nThe referenced key must contain a PEM-encoded client certificate.\nUse ClientKeySecretRef to provide the corresponding private key.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "clientKeySecretRef": { + "description": "ClientKeySecretRef references a Secret containing the private key for the client certificate.\nRequired when ClientCertSecretRef is set to enable mTLS.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "insecureSkipVerify": { + "description": "InsecureSkipVerify disables server certificate verification.\nWARNING: This should only be used for development/testing and not in production environments.", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "url": { + "description": "URL is the endpoint to call for this webhook. Must be an HTTP/HTTPS URL.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "name", + "url" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + } + }, + "type": "object", + "x-kubernetes-validations": [ + { + "message": "at least one validating or mutating webhook must be defined", + "rule": "(has(self.validating) ? size(self.validating) : 0) + (has(self.mutating) ? size(self.mutating) : 0) > 0" + } + ], + "additionalProperties": false + }, + "status": { + "description": "MCPWebhookConfigStatus defines the observed state of MCPWebhookConfig", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "configHash": { + "description": "ConfigHash is a hash of the spec, used for detecting changes", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration is the last observed generation corresponding to the current status", + "format": "int64", + "type": "integer" + }, + "referencingWorkloads": { + "description": "ReferencingWorkloads is a list of workload resources that reference this MCPWebhookConfig.\nEach entry identifies the workload by kind and name.", + "items": { + "description": "WorkloadReference identifies a workload that references a shared configuration resource.\nNamespace is implicit \u2014 cross-namespace references are not supported.", + "properties": { + "kind": { + "description": "Kind is the type of workload resource", + "enum": [ + "MCPServer", + "VirtualMCPServer", + "MCPRemoteProxy" + ], + "type": "string" + }, + "name": { + "description": "Name is the name of the workload resource", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/master-standalone/podlogs-stable-v1alpha2.json b/crdSchemas/master-standalone/podlogs-stable-v1alpha2.json new file mode 100644 index 0000000..36e4446 --- /dev/null +++ b/crdSchemas/master-standalone/podlogs-stable-v1alpha2.json @@ -0,0 +1,183 @@ +{ + "description": "PodLogs defines how to collect logs for a Pod.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "PodLogsSpec defines how to collect logs for a Pod.", + "properties": { + "namespaceSelector": { + "description": "Selector to select which namespaces the Pod objects are discovered from.", + "properties": { + "matchExpressions": { + "description": "matchExpressions is a list of label selector requirements. The requirements are ANDed.", + "items": { + "description": "A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.", + "properties": { + "key": { + "description": "key is the label key that the selector applies to.", + "type": "string" + }, + "operator": { + "description": "operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.", + "type": "string" + }, + "values": { + "description": "values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "description": "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.", + "type": "object" + } + }, + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + }, + "relabelings": { + "description": "RelabelConfigs to apply to logs before delivering.", + "items": { + "description": "RelabelConfig allows dynamic rewriting of the label set, being applied to samples before ingestion. It defines ``-section of Prometheus configuration. More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs", + "properties": { + "action": { + "default": "replace", + "description": "Action to perform based on regex matching. Default is 'replace'. uppercase and lowercase actions require Prometheus >= 2.36.", + "enum": [ + "replace", + "Replace", + "keep", + "Keep", + "drop", + "Drop", + "hashmod", + "HashMod", + "labelmap", + "LabelMap", + "labeldrop", + "LabelDrop", + "labelkeep", + "LabelKeep", + "lowercase", + "Lowercase", + "uppercase", + "Uppercase" + ], + "type": "string" + }, + "modulus": { + "description": "Modulus to take of the hash of the source label values.", + "format": "int64", + "type": "integer" + }, + "regex": { + "description": "Regular expression against which the extracted value is matched. Default is '(.*)'", + "type": "string" + }, + "replacement": { + "description": "Replacement value against which a regex replace is performed if the regular expression matches. Regex capture groups are available. Default is '$1'", + "type": "string" + }, + "separator": { + "description": "Separator placed between concatenated source label values. default is ';'.", + "type": "string" + }, + "sourceLabels": { + "description": "The source labels select values from existing labels. Their content is concatenated using the configured separator and matched against the configured regular expression for the replace, keep, and drop actions.", + "items": { + "description": "LabelName is a valid Prometheus label name which may only contain ASCII letters, numbers, as well as underscores.", + "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$", + "type": "string" + }, + "type": "array" + }, + "targetLabel": { + "description": "Label to which the resulting value is written in a replace action. It is mandatory for replace actions. Regex capture groups are available.", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "type": "array" + }, + "selector": { + "description": "Selector to select Pod objects. Required.", + "properties": { + "matchExpressions": { + "description": "matchExpressions is a list of label selector requirements. The requirements are ANDed.", + "items": { + "description": "A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.", + "properties": { + "key": { + "description": "key is the label key that the selector applies to.", + "type": "string" + }, + "operator": { + "description": "operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.", + "type": "string" + }, + "values": { + "description": "values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "description": "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.", + "type": "object" + } + }, + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + } + }, + "required": [ + "selector" + ], + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/master-standalone/virtualmcpcompositetooldefinition-stable-v1alpha1.json b/crdSchemas/master-standalone/virtualmcpcompositetooldefinition-stable-v1alpha1.json new file mode 100644 index 0000000..f12c087 --- /dev/null +++ b/crdSchemas/master-standalone/virtualmcpcompositetooldefinition-stable-v1alpha1.json @@ -0,0 +1,352 @@ +{ + "description": "VirtualMCPCompositeToolDefinition is the deprecated v1alpha1 version of the VirtualMCPCompositeToolDefinition resource.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "VirtualMCPCompositeToolDefinitionSpec defines the desired state of VirtualMCPCompositeToolDefinition.\nThis embeds the CompositeToolConfig from pkg/vmcp/config to share the configuration model\nbetween CLI and operator usage.", + "properties": { + "description": { + "description": "Description describes what the workflow does.", + "type": "string" + }, + "name": { + "description": "Name is the workflow name (unique identifier).", + "type": "string" + }, + "output": { + "description": "Output defines the structured output schema for this workflow.\nIf not specified, the workflow returns the last step's output (backward compatible).", + "properties": { + "properties": { + "additionalProperties": { + "description": "OutputProperty defines a single output property.\nFor non-object types, Value is required.\nFor object types, either Value or Properties must be specified (but not both).", + "properties": { + "default": { + "description": "Default is the fallback value if template expansion fails.\nType coercion is applied to match the declared Type.", + "x-kubernetes-preserve-unknown-fields": true + }, + "description": { + "description": "Description is a human-readable description exposed to clients and models", + "type": "string" + }, + "properties": { + "description": "Properties defines nested properties for object types.\nEach nested property has full metadata (type, description, value/properties).", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "type": { + "description": "Type is the JSON Schema type: \"string\", \"integer\", \"number\", \"boolean\", \"object\", \"array\"", + "enum": [ + "string", + "integer", + "number", + "boolean", + "object", + "array" + ], + "type": "string" + }, + "value": { + "description": "Value is a template string for constructing the runtime value.\nFor object types, this can be a JSON string that will be deserialized.\nSupports template syntax: {{.steps.step_id.output.field}}, {{.params.param_name}}", + "type": "string" + }, + "additionalProperties": false + }, + "required": [ + "type" + ], + "type": "object", + "additionalProperties": false + }, + "description": "Properties defines the output properties.\nMap key is the property name, value is the property definition.", + "type": "object" + }, + "required": { + "description": "Required lists property names that must be present in the output.", + "items": { + "type": "string" + }, + "type": "array" + }, + "additionalProperties": false + }, + "required": [ + "properties" + ], + "type": "object", + "additionalProperties": false + }, + "parameters": { + "description": "Parameters defines input parameter schema in JSON Schema format.\nShould be a JSON Schema object with \"type\": \"object\" and \"properties\".\nExample:\n {\n \"type\": \"object\",\n \"properties\": {\n \"param1\": {\"type\": \"string\", \"default\": \"value\"},\n \"param2\": {\"type\": \"integer\"}\n },\n \"required\": [\"param2\"]\n }\n\nWe use json.Map rather than a typed struct because JSON Schema is highly\nflexible with many optional fields (default, enum, minimum, maximum, pattern,\nitems, additionalProperties, oneOf, anyOf, allOf, etc.). Using json.Map\nallows full JSON Schema compatibility without needing to define every possible\nfield, and matches how the MCP SDK handles inputSchema.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "steps": { + "description": "Steps are the workflow steps to execute.", + "items": { + "description": "WorkflowStepConfig defines a single workflow step.\nThis matches the proposal's step configuration (lines 180-255).", + "properties": { + "arguments": { + "description": "Arguments is a map of argument values with template expansion support.\nSupports Go template syntax with .params and .steps for string values.\nNon-string values (integers, booleans, arrays, objects) are passed as-is.\nNote: the templating is only supported on the first level of the key-value pairs.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "collection": { + "description": "Collection is a Go template expression that resolves to a JSON array or a slice.\nOnly used when Type is \"forEach\".", + "type": "string" + }, + "condition": { + "description": "Condition is a template expression that determines if the step should execute", + "type": "string" + }, + "defaultResults": { + "description": "DefaultResults provides fallback output values when this step is skipped\n(due to condition evaluating to false) or fails (when onError.action is \"continue\").\nEach key corresponds to an output field name referenced by downstream steps.\nRequired if the step may be skipped AND downstream steps reference this step's output.", + "x-kubernetes-preserve-unknown-fields": true + }, + "dependsOn": { + "description": "DependsOn lists step IDs that must complete before this step", + "items": { + "type": "string" + }, + "type": "array" + }, + "id": { + "description": "ID is the unique identifier for this step.", + "type": "string" + }, + "itemVar": { + "description": "ItemVar is the variable name used to reference the current item in forEach templates.\nDefaults to \"item\" if not specified.\nOnly used when Type is \"forEach\".", + "type": "string" + }, + "maxIterations": { + "description": "MaxIterations limits the number of items that can be iterated over.\nDefaults to 100, hard cap at 1000.\nOnly used when Type is \"forEach\".", + "type": "integer" + }, + "maxParallel": { + "description": "MaxParallel limits the number of concurrent iterations in a forEach step.\nDefaults to the DAG executor's maxParallel (10).\nOnly used when Type is \"forEach\".", + "type": "integer" + }, + "message": { + "description": "Message is the elicitation message\nOnly used when Type is \"elicitation\"", + "type": "string" + }, + "onCancel": { + "description": "OnCancel defines the action to take when the user cancels/dismisses the elicitation\nOnly used when Type is \"elicitation\"", + "properties": { + "action": { + "default": "abort", + "description": "Action defines the action to take when the user declines or cancels\n- skip_remaining: Skip remaining steps in the workflow\n- abort: Abort the entire workflow execution\n- continue: Continue to the next step", + "enum": [ + "skip_remaining", + "abort", + "continue" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "onDecline": { + "description": "OnDecline defines the action to take when the user explicitly declines the elicitation\nOnly used when Type is \"elicitation\"", + "properties": { + "action": { + "default": "abort", + "description": "Action defines the action to take when the user declines or cancels\n- skip_remaining: Skip remaining steps in the workflow\n- abort: Abort the entire workflow execution\n- continue: Continue to the next step", + "enum": [ + "skip_remaining", + "abort", + "continue" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "onError": { + "description": "OnError defines error handling behavior", + "properties": { + "action": { + "default": "abort", + "description": "Action defines the action to take on error", + "enum": [ + "abort", + "continue", + "retry" + ], + "type": "string" + }, + "retryCount": { + "description": "RetryCount is the maximum number of retries\nOnly used when Action is \"retry\"", + "type": "integer" + }, + "retryDelay": { + "description": "RetryDelay is the delay between retry attempts\nOnly used when Action is \"retry\"", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "schema": { + "description": "Schema defines the expected response schema for elicitation", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "step": { + "description": "InnerStep defines the step to execute for each item in the collection.\nOnly used when Type is \"forEach\". Only tool-type inner steps are supported.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "timeout": { + "description": "Timeout is the maximum execution time for this step", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "tool": { + "description": "Tool is the tool to call (format: \"workload.tool_name\")\nOnly used when Type is \"tool\"", + "type": "string" + }, + "type": { + "default": "tool", + "description": "Type is the step type (tool, elicitation, etc.)", + "enum": [ + "tool", + "elicitation", + "forEach" + ], + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + }, + "timeout": { + "description": "Timeout is the maximum workflow execution time.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + } + }, + "required": [ + "name", + "steps" + ], + "type": "object", + "additionalProperties": false + }, + "status": { + "description": "VirtualMCPCompositeToolDefinitionStatus defines the observed state of VirtualMCPCompositeToolDefinition", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations of the workflow's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "observedGeneration": { + "description": "ObservedGeneration is the most recent generation observed for this VirtualMCPCompositeToolDefinition\nIt corresponds to the resource's generation, which is updated on mutation by the API Server", + "format": "int64", + "type": "integer" + }, + "referencingVirtualServers": { + "description": "ReferencingVirtualServers lists VirtualMCPServer resources that reference this workflow\nThis helps track which servers need to be reconciled when this workflow changes", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "set" + }, + "validationErrors": { + "description": "ValidationErrors contains validation error messages if ValidationStatus is Invalid", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "validationStatus": { + "description": "ValidationStatus indicates the validation state of the workflow\n- Valid: Workflow structure is valid\n- Invalid: Workflow has validation errors", + "enum": [ + "Valid", + "Invalid", + "Unknown" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/master-standalone/virtualmcpcompositetooldefinition-stable-v1beta1.json b/crdSchemas/master-standalone/virtualmcpcompositetooldefinition-stable-v1beta1.json new file mode 100644 index 0000000..a7af011 --- /dev/null +++ b/crdSchemas/master-standalone/virtualmcpcompositetooldefinition-stable-v1beta1.json @@ -0,0 +1,352 @@ +{ + "description": "VirtualMCPCompositeToolDefinition is the Schema for the virtualmcpcompositetooldefinitions API\nVirtualMCPCompositeToolDefinition defines reusable composite workflows that can be referenced\nby multiple VirtualMCPServer instances", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "VirtualMCPCompositeToolDefinitionSpec defines the desired state of VirtualMCPCompositeToolDefinition.\nThis embeds the CompositeToolConfig from pkg/vmcp/config to share the configuration model\nbetween CLI and operator usage.", + "properties": { + "description": { + "description": "Description describes what the workflow does.", + "type": "string" + }, + "name": { + "description": "Name is the workflow name (unique identifier).", + "type": "string" + }, + "output": { + "description": "Output defines the structured output schema for this workflow.\nIf not specified, the workflow returns the last step's output (backward compatible).", + "properties": { + "properties": { + "additionalProperties": { + "description": "OutputProperty defines a single output property.\nFor non-object types, Value is required.\nFor object types, either Value or Properties must be specified (but not both).", + "properties": { + "default": { + "description": "Default is the fallback value if template expansion fails.\nType coercion is applied to match the declared Type.", + "x-kubernetes-preserve-unknown-fields": true + }, + "description": { + "description": "Description is a human-readable description exposed to clients and models", + "type": "string" + }, + "properties": { + "description": "Properties defines nested properties for object types.\nEach nested property has full metadata (type, description, value/properties).", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "type": { + "description": "Type is the JSON Schema type: \"string\", \"integer\", \"number\", \"boolean\", \"object\", \"array\"", + "enum": [ + "string", + "integer", + "number", + "boolean", + "object", + "array" + ], + "type": "string" + }, + "value": { + "description": "Value is a template string for constructing the runtime value.\nFor object types, this can be a JSON string that will be deserialized.\nSupports template syntax: {{.steps.step_id.output.field}}, {{.params.param_name}}", + "type": "string" + }, + "additionalProperties": false + }, + "required": [ + "type" + ], + "type": "object", + "additionalProperties": false + }, + "description": "Properties defines the output properties.\nMap key is the property name, value is the property definition.", + "type": "object" + }, + "required": { + "description": "Required lists property names that must be present in the output.", + "items": { + "type": "string" + }, + "type": "array" + }, + "additionalProperties": false + }, + "required": [ + "properties" + ], + "type": "object", + "additionalProperties": false + }, + "parameters": { + "description": "Parameters defines input parameter schema in JSON Schema format.\nShould be a JSON Schema object with \"type\": \"object\" and \"properties\".\nExample:\n {\n \"type\": \"object\",\n \"properties\": {\n \"param1\": {\"type\": \"string\", \"default\": \"value\"},\n \"param2\": {\"type\": \"integer\"}\n },\n \"required\": [\"param2\"]\n }\n\nWe use json.Map rather than a typed struct because JSON Schema is highly\nflexible with many optional fields (default, enum, minimum, maximum, pattern,\nitems, additionalProperties, oneOf, anyOf, allOf, etc.). Using json.Map\nallows full JSON Schema compatibility without needing to define every possible\nfield, and matches how the MCP SDK handles inputSchema.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "steps": { + "description": "Steps are the workflow steps to execute.", + "items": { + "description": "WorkflowStepConfig defines a single workflow step.\nThis matches the proposal's step configuration (lines 180-255).", + "properties": { + "arguments": { + "description": "Arguments is a map of argument values with template expansion support.\nSupports Go template syntax with .params and .steps for string values.\nNon-string values (integers, booleans, arrays, objects) are passed as-is.\nNote: the templating is only supported on the first level of the key-value pairs.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "collection": { + "description": "Collection is a Go template expression that resolves to a JSON array or a slice.\nOnly used when Type is \"forEach\".", + "type": "string" + }, + "condition": { + "description": "Condition is a template expression that determines if the step should execute", + "type": "string" + }, + "defaultResults": { + "description": "DefaultResults provides fallback output values when this step is skipped\n(due to condition evaluating to false) or fails (when onError.action is \"continue\").\nEach key corresponds to an output field name referenced by downstream steps.\nRequired if the step may be skipped AND downstream steps reference this step's output.", + "x-kubernetes-preserve-unknown-fields": true + }, + "dependsOn": { + "description": "DependsOn lists step IDs that must complete before this step", + "items": { + "type": "string" + }, + "type": "array" + }, + "id": { + "description": "ID is the unique identifier for this step.", + "type": "string" + }, + "itemVar": { + "description": "ItemVar is the variable name used to reference the current item in forEach templates.\nDefaults to \"item\" if not specified.\nOnly used when Type is \"forEach\".", + "type": "string" + }, + "maxIterations": { + "description": "MaxIterations limits the number of items that can be iterated over.\nDefaults to 100, hard cap at 1000.\nOnly used when Type is \"forEach\".", + "type": "integer" + }, + "maxParallel": { + "description": "MaxParallel limits the number of concurrent iterations in a forEach step.\nDefaults to the DAG executor's maxParallel (10).\nOnly used when Type is \"forEach\".", + "type": "integer" + }, + "message": { + "description": "Message is the elicitation message\nOnly used when Type is \"elicitation\"", + "type": "string" + }, + "onCancel": { + "description": "OnCancel defines the action to take when the user cancels/dismisses the elicitation\nOnly used when Type is \"elicitation\"", + "properties": { + "action": { + "default": "abort", + "description": "Action defines the action to take when the user declines or cancels\n- skip_remaining: Skip remaining steps in the workflow\n- abort: Abort the entire workflow execution\n- continue: Continue to the next step", + "enum": [ + "skip_remaining", + "abort", + "continue" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "onDecline": { + "description": "OnDecline defines the action to take when the user explicitly declines the elicitation\nOnly used when Type is \"elicitation\"", + "properties": { + "action": { + "default": "abort", + "description": "Action defines the action to take when the user declines or cancels\n- skip_remaining: Skip remaining steps in the workflow\n- abort: Abort the entire workflow execution\n- continue: Continue to the next step", + "enum": [ + "skip_remaining", + "abort", + "continue" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "onError": { + "description": "OnError defines error handling behavior", + "properties": { + "action": { + "default": "abort", + "description": "Action defines the action to take on error", + "enum": [ + "abort", + "continue", + "retry" + ], + "type": "string" + }, + "retryCount": { + "description": "RetryCount is the maximum number of retries\nOnly used when Action is \"retry\"", + "type": "integer" + }, + "retryDelay": { + "description": "RetryDelay is the delay between retry attempts\nOnly used when Action is \"retry\"", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "schema": { + "description": "Schema defines the expected response schema for elicitation", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "step": { + "description": "InnerStep defines the step to execute for each item in the collection.\nOnly used when Type is \"forEach\". Only tool-type inner steps are supported.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "timeout": { + "description": "Timeout is the maximum execution time for this step", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "tool": { + "description": "Tool is the tool to call (format: \"workload.tool_name\")\nOnly used when Type is \"tool\"", + "type": "string" + }, + "type": { + "default": "tool", + "description": "Type is the step type (tool, elicitation, etc.)", + "enum": [ + "tool", + "elicitation", + "forEach" + ], + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + }, + "timeout": { + "description": "Timeout is the maximum workflow execution time.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + } + }, + "required": [ + "name", + "steps" + ], + "type": "object", + "additionalProperties": false + }, + "status": { + "description": "VirtualMCPCompositeToolDefinitionStatus defines the observed state of VirtualMCPCompositeToolDefinition", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations of the workflow's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "observedGeneration": { + "description": "ObservedGeneration is the most recent generation observed for this VirtualMCPCompositeToolDefinition\nIt corresponds to the resource's generation, which is updated on mutation by the API Server", + "format": "int64", + "type": "integer" + }, + "referencingVirtualServers": { + "description": "ReferencingVirtualServers lists VirtualMCPServer resources that reference this workflow\nThis helps track which servers need to be reconciled when this workflow changes", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "set" + }, + "validationErrors": { + "description": "ValidationErrors contains validation error messages if ValidationStatus is Invalid", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "validationStatus": { + "description": "ValidationStatus indicates the validation state of the workflow\n- Valid: Workflow structure is valid\n- Invalid: Workflow has validation errors", + "enum": [ + "Valid", + "Invalid", + "Unknown" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/master-standalone/virtualmcpserver-stable-v1alpha1.json b/crdSchemas/master-standalone/virtualmcpserver-stable-v1alpha1.json new file mode 100644 index 0000000..b11eefb --- /dev/null +++ b/crdSchemas/master-standalone/virtualmcpserver-stable-v1alpha1.json @@ -0,0 +1,2694 @@ +{ + "description": "VirtualMCPServer is the deprecated v1alpha1 version of the VirtualMCPServer resource.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "VirtualMCPServerSpec defines the desired state of VirtualMCPServer", + "properties": { + "authServerConfig": { + "description": "AuthServerConfig configures an embedded OAuth authorization server.\nWhen set, the vMCP server acts as an OIDC issuer, drives users through\nupstream IDPs, and issues ToolHive JWTs. The embedded AS becomes the\nIncomingAuth OIDC provider \u2014 its issuer must match IncomingAuth.OIDCConfigRef\nso that tokens it issues are accepted by the vMCP's incoming auth middleware.\nWhen nil, IncomingAuth uses an external IDP and behavior is unchanged.", + "properties": { + "authorizationEndpointBaseUrl": { + "description": "AuthorizationEndpointBaseURL overrides the base URL used for the authorization_endpoint\nin the OAuth discovery document. When set, the discovery document will advertise\n`{authorizationEndpointBaseUrl}/oauth/authorize` instead of `{issuer}/oauth/authorize`.\nAll other endpoints (token, registration, JWKS) remain derived from the issuer.\nThis is useful when the browser-facing authorization endpoint needs to be on a\ndifferent host than the issuer used for backend-to-backend calls.\nMust be a valid HTTPS URL (or HTTP for localhost) without query, fragment, or trailing slash.", + "pattern": "^https?://[^\\s?#]+[^/\\s?#]$", + "type": "string" + }, + "baselineClientScopes": { + "description": "BaselineClientScopes is a baseline set of OAuth 2.0 scopes guaranteed to be\nincluded in every client registration. The embedded auth server unions these\nscopes into the registered set returned by RFC 7591 Dynamic Client\nRegistration, so a client that narrows the `scope` field at /oauth/register\ncan still request the baseline scopes at /oauth/authorize. All values must\nbe present in the upstream-derived scopesSupported set; the auth server\nfails to start if any value is missing.\n\nSecurity: every client registered via /oauth/register will gain the\nability to request these scopes at /oauth/authorize, regardless of what\nthe client itself requested. Keep the baseline narrow (typically\n\"openid\" and \"offline_access\"). Adding a privileged scope here \u2014 e.g.\n\"admin:read\" \u2014 would grant it to every DCR-registered client, including\npublic clients like Claude Code, Cursor, and VS Code.", + "items": { + "minLength": 1, + "pattern": "^[\\x21\\x23-\\x5B\\x5D-\\x7E]+$", + "type": "string" + }, + "maxItems": 10, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "hmacSecretRefs": { + "description": "HMACSecretRefs references Kubernetes Secrets containing symmetric secrets for signing\nauthorization codes and refresh tokens (opaque tokens).\nCurrent secret must be at least 32 bytes and cryptographically random.\nSupports secret rotation via multiple entries (first is current, rest are for verification).\nIf not specified, an ephemeral secret will be auto-generated (development only -\nauth codes and refresh tokens will be invalid after restart).", + "items": { + "description": "SecretKeyRef is a reference to a key within a Secret", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "issuer": { + "description": "Issuer is the issuer identifier for this authorization server.\nThis will be included in the \"iss\" claim of issued tokens.\nMust be a valid HTTPS URL (or HTTP for localhost) without query, fragment, or trailing slash (per RFC 8414).", + "pattern": "^https?://[^\\s?#]+[^/\\s?#]$", + "type": "string" + }, + "primaryUpstreamProvider": { + "description": "PrimaryUpstreamProvider names the upstream IDP whose access token Cedar\nshould read claims from when authorising a request. Must match the name\nof one of the entries in UpstreamProviders. When empty, the controller\nauto-selects the first entry of UpstreamProviders.\n\nOnly meaningful on VirtualMCPServer, where multiple upstream providers\ncan be configured and Cedar needs to pick which token's claims to\nevaluate. The VirtualMCPServer controller validates this field against\nUpstreamProviders at admission and rejects unresolvable values.\n\nOn MCPServer and MCPRemoteProxy this field is structurally present (the\nEmbeddedAuthServerConfig struct is shared) but has no runtime effect:\nthose CRDs are restricted to a single upstream so there is no choice to\nmake. Setting it on those CRDs is silently ignored.", + "maxLength": 63, + "minLength": 1, + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "type": "string" + }, + "signingKeySecretRefs": { + "description": "SigningKeySecretRefs references Kubernetes Secrets containing signing keys for JWT operations.\nSupports key rotation by allowing multiple keys (oldest keys are used for verification only).\nIf not specified, an ephemeral signing key will be auto-generated (development only -\nJWTs will be invalid after restart).", + "items": { + "description": "SecretKeyRef is a reference to a key within a Secret", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "maxItems": 5, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "storage": { + "description": "Storage configures the storage backend for the embedded auth server.\nIf not specified, defaults to in-memory storage.", + "properties": { + "redis": { + "description": "Redis configures the Redis storage backend.\nRequired when type is \"redis\".", + "properties": { + "aclUserConfig": { + "description": "ACLUserConfig configures Redis ACL user authentication.", + "properties": { + "passwordSecretRef": { + "description": "PasswordSecretRef references a Secret containing the Redis ACL password.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "usernameSecretRef": { + "description": "UsernameSecretRef references a Secret containing the Redis ACL username.\nWhen omitted, connections use legacy password-only AUTH. Omit for managed\nRedis tiers that do not support ACL users (e.g. GCP Memorystore Basic/Standard\nHA, Azure Cache for Redis). Set for services that support ACL users (e.g. AWS\nElastiCache non-cluster with Redis 6+ RBAC).", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "passwordSecretRef" + ], + "type": "object", + "additionalProperties": false + }, + "addr": { + "description": "Addr is the Redis server address (host:port). Required for standalone and cluster modes.\nUse for managed Redis services that expose a single endpoint (GCP Memorystore basic tier,\nAWS ElastiCache without cluster mode, or cluster-mode services when clusterMode is true).\nMutually exclusive with sentinelConfig.", + "type": "string" + }, + "clusterMode": { + "description": "ClusterMode enables the Redis Cluster protocol. Set to true when addr points to a\nRedis Cluster discovery endpoint (e.g., GCP Memorystore Cluster, AWS ElastiCache\ncluster mode enabled). Requires addr to be set.", + "type": "boolean" + }, + "dialTimeout": { + "default": "5s", + "description": "DialTimeout is the timeout for establishing connections.\nFormat: Go duration string (e.g., \"5s\", \"1m\").", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "readTimeout": { + "default": "3s", + "description": "ReadTimeout is the timeout for socket reads.\nFormat: Go duration string (e.g., \"3s\", \"1m\").", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "sentinelConfig": { + "description": "SentinelConfig holds Redis Sentinel configuration.\nUse for self-managed Redis with Sentinel-based HA. Mutually exclusive with addr.", + "properties": { + "db": { + "default": 0, + "description": "DB is the Redis database number.", + "format": "int32", + "type": "integer" + }, + "masterName": { + "description": "MasterName is the name of the Redis master monitored by Sentinel.", + "type": "string" + }, + "sentinelAddrs": { + "description": "SentinelAddrs is a list of Sentinel host:port addresses.\nMutually exclusive with SentinelService.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "sentinelService": { + "description": "SentinelService enables automatic discovery from a Kubernetes Service.\nMutually exclusive with SentinelAddrs.", + "properties": { + "name": { + "description": "Name of the Sentinel Service.", + "type": "string" + }, + "namespace": { + "description": "Namespace of the Sentinel Service (defaults to same namespace).", + "type": "string" + }, + "port": { + "default": 26379, + "description": "Port of the Sentinel service.", + "format": "int32", + "type": "integer" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "masterName" + ], + "type": "object", + "additionalProperties": false + }, + "sentinelTls": { + "description": "SentinelTLS configures TLS for connections to Sentinel instances.\nOnly applies when sentinelConfig is set. Presence of this field enables TLS.", + "properties": { + "caCertSecretRef": { + "description": "CACertSecretRef references a Secret containing a PEM-encoded CA certificate\nfor verifying the server. When not specified, system root CAs are used.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "insecureSkipVerify": { + "description": "InsecureSkipVerify skips TLS certificate verification.\nUse when connecting to services with self-signed certificates.", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "tls": { + "description": "TLS configures TLS for connections to the Redis/Valkey master.\nPresence of this field enables TLS. Omit to use plaintext.", + "properties": { + "caCertSecretRef": { + "description": "CACertSecretRef references a Secret containing a PEM-encoded CA certificate\nfor verifying the server. When not specified, system root CAs are used.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "insecureSkipVerify": { + "description": "InsecureSkipVerify skips TLS certificate verification.\nUse when connecting to services with self-signed certificates.", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "writeTimeout": { + "default": "3s", + "description": "WriteTimeout is the timeout for socket writes.\nFormat: Go duration string (e.g., \"3s\", \"1m\").", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + } + }, + "required": [ + "aclUserConfig" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "exactly one of addr or sentinelConfig must be set", + "rule": "(has(self.addr) && self.addr.size() > 0) != has(self.sentinelConfig)" + }, + { + "message": "clusterMode requires addr to be set", + "rule": "!(has(self.clusterMode) && self.clusterMode) || (has(self.addr) && self.addr.size() > 0)" + } + ], + "additionalProperties": false + }, + "type": { + "default": "memory", + "description": "Type specifies the storage backend type.\nValid values: \"memory\" (default), \"redis\".", + "enum": [ + "memory", + "redis" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "tokenLifespans": { + "description": "TokenLifespans configures the duration that various tokens are valid.\nIf not specified, defaults are applied (access: 1h, refresh: 7d, authCode: 10m).", + "properties": { + "accessTokenLifespan": { + "description": "AccessTokenLifespan is the duration that access tokens are valid.\nFormat: Go duration string (e.g., \"1h\", \"30m\", \"24h\").\nIf empty, defaults to 1 hour.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "authCodeLifespan": { + "description": "AuthCodeLifespan is the duration that authorization codes are valid.\nFormat: Go duration string (e.g., \"10m\", \"5m\").\nIf empty, defaults to 10 minutes.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "refreshTokenLifespan": { + "description": "RefreshTokenLifespan is the duration that refresh tokens are valid.\nFormat: Go duration string (e.g., \"168h\", \"7d\" as \"168h\").\nIf empty, defaults to 7 days (168h).", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "upstreamProviders": { + "description": "UpstreamProviders configures connections to upstream Identity Providers.\nThe embedded auth server delegates authentication to these providers.\nMCPServer and MCPRemoteProxy support a single upstream; VirtualMCPServer supports multiple.", + "items": { + "description": "UpstreamProviderConfig defines configuration for an upstream Identity Provider.\n\nExactly one of OIDCConfig or OAuth2Config must be set and must match the\ndeclared Type: oidc-typed providers set OIDCConfig, oauth2-typed providers\nset OAuth2Config. The CEL rule below enforces the pairing at admission; the\nmatching Go-level check in validateUpstreamProvider provides defense-in-depth\nfor stored objects.\n\nThe rule is structured as a chain of equality checks ending in an explicit\n`false`, so adding a new UpstreamProviderType value without extending this\nrule fails admission instead of silently demanding the OAuth2 shape. When\nadding a new type, extend both this rule and validateUpstreamProvider.", + "properties": { + "name": { + "description": "Name uniquely identifies this upstream provider.\nUsed for routing decisions and session binding in multi-upstream scenarios.\nMust be lowercase alphanumeric with hyphens (DNS-label-like).", + "maxLength": 63, + "minLength": 1, + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "type": "string" + }, + "oauth2Config": { + "description": "OAuth2Config contains OAuth 2.0-specific configuration.\nRequired when Type is \"oauth2\", must be nil when Type is \"oidc\".", + "properties": { + "additionalAuthorizationParams": { + "additionalProperties": { + "type": "string" + }, + "description": "AdditionalAuthorizationParams are extra query parameters to include in\nauthorization requests sent to the upstream provider.\nThis is useful for providers that require custom parameters, such as\nGoogle's access_type=offline for obtaining refresh tokens.\nFramework-managed parameters (response_type, client_id, redirect_uri,\nscope, state, code_challenge, code_challenge_method, nonce) are not allowed.", + "maxProperties": 16, + "type": "object" + }, + "authorizationEndpoint": { + "description": "AuthorizationEndpoint is the URL for the OAuth authorization endpoint.", + "pattern": "^https?://.*$", + "type": "string" + }, + "clientId": { + "description": "ClientID is the OAuth 2.0 client identifier registered with the upstream IDP.\nMutually exclusive with DCRConfig: when DCRConfig is set, ClientID is obtained\nat runtime via RFC 7591 Dynamic Client Registration and must be left empty.", + "type": "string" + }, + "clientSecretRef": { + "description": "ClientSecretRef references a Kubernetes Secret containing the OAuth 2.0 client secret.\nOptional for public clients using PKCE instead of client secret.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "dcrConfig": { + "description": "DCRConfig enables RFC 7591 Dynamic Client Registration against the upstream\nauthorization server. When set, the client credentials are obtained at\nruntime rather than being pre-provisioned, and ClientID must be left empty.\nMutually exclusive with ClientID.", + "properties": { + "discoveryUrl": { + "description": "DiscoveryURL is the RFC 8414 / OIDC Discovery document URL. The resolver\nissues a single GET against this URL (no well-known-path fallback) and\nreads registration_endpoint, authorization_endpoint, token_endpoint,\ntoken_endpoint_auth_methods_supported, and scopes_supported from the\nresponse.\nMutually exclusive with RegistrationEndpoint.\nHTTPS is required because the registration endpoint resolved from this\ndocument carries the initial access token and the issued client_secret\n(RFC 7591 \u00a73, RFC 8414 \u00a73). MaxLength is a defensive size cap (etcd\nobject budget, regex evaluation cost) and matches the conventional URL\nlength cap.", + "maxLength": 2048, + "pattern": "^https://[^\\s?#]+[^/\\s?#]$", + "type": "string" + }, + "initialAccessTokenRef": { + "description": "InitialAccessTokenRef is an optional reference to a Kubernetes Secret\ncarrying an RFC 7591 \u00a73 initial access token. When set, the resolver\npresents the token value as a Bearer credential on the registration\nrequest. Mirrors the ClientSecretRef pattern.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "registrationEndpoint": { + "description": "RegistrationEndpoint is the RFC 7591 registration endpoint URL used\ndirectly, bypassing discovery. When using this field, the caller is\nexpected to also supply AuthorizationEndpoint, TokenEndpoint, and an\nexplicit Scopes list on the parent OAuth2UpstreamConfig.\nMutually exclusive with DiscoveryURL.\nHTTPS is required because the registration endpoint carries the initial\naccess token and the issued client_secret (RFC 7591 \u00a73, RFC 8414 \u00a73).\nMaxLength is a defensive size cap (etcd object budget, regex evaluation\ncost) and matches the conventional URL length cap.", + "maxLength": 2048, + "pattern": "^https://[^\\s?#]+[^/\\s?#]$", + "type": "string" + }, + "softwareId": { + "description": "SoftwareID is the RFC 7591 \"software_id\" registration metadata value,\nidentifying the client software independent of any particular\nregistration instance. Typically a UUID or short identifier.", + "maxLength": 255, + "type": "string" + }, + "softwareStatement": { + "description": "SoftwareStatement is the RFC 7591 \"software_statement\" JWT asserting\nmetadata about the client software, signed by a party the authorization\nserver trusts.\n\nStored inline on the CR. The JWT is signed but not encrypted, so its\ncontents are visible to anyone with get/list/watch on this resource and\nappear in etcd backups in plaintext. Treat the value as non-confidential\n(signed attestation, not a secret). Operators that rotate software\nstatements like bearer credentials should keep them at the authorization\nserver side and rely on the registration endpoint's initial access\ntoken (see InitialAccessTokenRef) instead of placing them on the CR.\n\nBounded to 16384 characters as a defensive size cap (etcd object\nbudget, regex evaluation cost). Real-world signed statements with\nembedded x5c certificate chains, JWKS keys, or OIDC-Federation\ntrust-framework metadata routinely exceed 4 KB.", + "maxLength": 16384, + "type": "string" + } + }, + "type": "object", + "x-kubernetes-validations": [ + { + "message": "exactly one of discoveryUrl or registrationEndpoint must be set", + "rule": "has(self.discoveryUrl) != has(self.registrationEndpoint)" + } + ], + "additionalProperties": false + }, + "identityFromToken": { + "description": "IdentityFromToken extracts user identity (subject, name, email) directly\nfrom the OAuth2 token-endpoint response body using gjson dot-notation paths.\nWhen set, the embedded auth server skips the userinfo HTTP call entirely\nand resolves identity from the token response. See IdentityFromTokenConfig\nfor trust-model and uniqueness considerations.", + "properties": { + "emailPath": { + "description": "EmailPath is the dot-notation path to the email address field in the token response.\nIf not specified or if the path does not resolve to a string, the email is omitted.\nOmit the field entirely rather than setting it to an empty string.", + "maxLength": 256, + "minLength": 1, + "type": "string" + }, + "namePath": { + "description": "NamePath is the dot-notation path to the display name field in the token response.\nIf not specified or if the path does not resolve to a string, the display name is omitted.\nOmit the field entirely rather than setting it to an empty string.", + "maxLength": 256, + "minLength": 1, + "type": "string" + }, + "subjectPath": { + "description": "SubjectPath is the dot-notation path to the subject (user ID) field in the token response.\nWarning: claims read from the token response are trusted only via TLS, not\ncryptographically verified; prefer OIDC ID tokens when verifiable claims are required.\nExample: \"authed_user.id\" for Slack (top-level token-response field). For providers\nwhose token response embeds the access token as a JWT (e.g. Snowflake), use the\n\"@upstreamjwt\" modifier to decode the payload, e.g. \"access_token|@upstreamjwt|sub\".\nThe \"@upstreamjwt\" modifier performs no signature verification either.", + "maxLength": 256, + "minLength": 1, + "type": "string" + } + }, + "required": [ + "subjectPath" + ], + "type": "object", + "additionalProperties": false + }, + "redirectUri": { + "description": "RedirectURI is the callback URL where the upstream IDP will redirect after authentication.\nWhen not specified, defaults to `{resourceUrl}/oauth/callback` where `resourceUrl` is the\nURL associated with the resource (e.g., MCPServer or vMCP) using this config.", + "type": "string" + }, + "scopes": { + "description": "Scopes are the OAuth scopes to request from the upstream IDP.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "tokenEndpoint": { + "description": "TokenEndpoint is the URL for the OAuth token endpoint.", + "pattern": "^https?://.*$", + "type": "string" + }, + "tokenResponseMapping": { + "description": "TokenResponseMapping configures custom field extraction from non-standard token responses.\nSome OAuth providers (e.g., GovSlack) nest token fields under non-standard paths\ninstead of returning them at the top level. When set, ToolHive performs the token\nexchange HTTP call directly and extracts fields using the configured dot-notation paths.\nIf nil, standard OAuth 2.0 token response parsing is used.\nFor extracting user identity from the token response, see IdentityFromToken.", + "properties": { + "accessTokenPath": { + "description": "AccessTokenPath is the dot-notation path to the access token in the response.\nExample: \"authed_user.access_token\"", + "minLength": 1, + "type": "string" + }, + "expiresInPath": { + "description": "ExpiresInPath is the dot-notation path to the expires_in value (in seconds).\nIf not specified, defaults to \"expires_in\".", + "type": "string" + }, + "refreshTokenPath": { + "description": "RefreshTokenPath is the dot-notation path to the refresh token in the response.\nIf not specified, defaults to \"refresh_token\".", + "type": "string" + }, + "scopePath": { + "description": "ScopePath is the dot-notation path to the scope string in the response.\nIf not specified, defaults to \"scope\".", + "type": "string" + } + }, + "required": [ + "accessTokenPath" + ], + "type": "object", + "additionalProperties": false + }, + "userInfo": { + "description": "UserInfo contains configuration for fetching user information from the upstream provider.\nWhen omitted and IdentityFromToken is also unset, the embedded auth server runs in\nsynthesis mode for this upstream: a non-PII subject derived from the access token, no\nName/Email. Use this shape for upstreams with no userinfo surface and no identity in\nthe token response (e.g., MCP authorization servers per the MCP spec). When\nIdentityFromToken is set instead, identity is resolved from the token response body\n(e.g., Snowflake's \"username\" field, Slack's \"authed_user.id\"); the userinfo HTTP call\nis skipped entirely.", + "properties": { + "additionalHeaders": { + "additionalProperties": { + "type": "string" + }, + "description": "AdditionalHeaders contains extra headers to include in the userinfo request.\nUseful for providers that require specific headers (e.g., GitHub's Accept header).", + "type": "object" + }, + "endpointUrl": { + "description": "EndpointURL is the URL of the userinfo endpoint.", + "pattern": "^https?://.*$", + "type": "string" + }, + "fieldMapping": { + "description": "FieldMapping contains custom field mapping configuration for non-standard providers.\nIf nil, standard OIDC field names are used (\"sub\", \"name\", \"email\").", + "properties": { + "emailFields": { + "description": "EmailFields is an ordered list of field names to try for the email address.\nThe first non-empty value found will be used.\nDefault: [\"email\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "nameFields": { + "description": "NameFields is an ordered list of field names to try for the display name.\nThe first non-empty value found will be used.\nDefault: [\"name\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "subjectFields": { + "description": "SubjectFields is an ordered list of field names to try for the user ID.\nThe first non-empty value found will be used.\nDefault: [\"sub\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "type": "object", + "additionalProperties": false + }, + "httpMethod": { + "description": "HTTPMethod is the HTTP method to use for the userinfo request.\nIf not specified, defaults to GET.", + "enum": [ + "GET", + "POST" + ], + "type": "string" + } + }, + "required": [ + "endpointUrl" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "authorizationEndpoint", + "tokenEndpoint" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "exactly one of clientId or dcrConfig must be set", + "rule": "(has(self.clientId) && size(self.clientId) > 0) ? !has(self.dcrConfig) : has(self.dcrConfig)" + }, + { + "message": "clientSecretRef must not be set when dcrConfig is set; the client_secret is obtained at runtime via Dynamic Client Registration", + "rule": "!(has(self.dcrConfig) && has(self.clientSecretRef))" + } + ], + "additionalProperties": false + }, + "oidcConfig": { + "description": "OIDCConfig contains OIDC-specific configuration.\nRequired when Type is \"oidc\", must be nil when Type is \"oauth2\".", + "properties": { + "additionalAuthorizationParams": { + "additionalProperties": { + "type": "string" + }, + "description": "AdditionalAuthorizationParams are extra query parameters to include in\nauthorization requests sent to the upstream provider.\nThis is useful for providers that require custom parameters, such as\nGoogle's access_type=offline for obtaining refresh tokens.\nNote: when using access_type=offline, also set explicit scopes to avoid\nthe default offline_access scope being sent alongside it.\nFramework-managed parameters (response_type, client_id, redirect_uri,\nscope, state, code_challenge, code_challenge_method, nonce) are not allowed.", + "maxProperties": 16, + "type": "object" + }, + "clientId": { + "description": "ClientID is the OAuth 2.0 client identifier registered with the upstream IDP.", + "type": "string" + }, + "clientSecretRef": { + "description": "ClientSecretRef references a Kubernetes Secret containing the OAuth 2.0 client secret.\nOptional for public clients using PKCE instead of client secret.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "issuerUrl": { + "description": "IssuerURL is the OIDC issuer URL for automatic endpoint discovery.\nMust be a valid HTTPS URL.", + "pattern": "^https://.*$", + "type": "string" + }, + "redirectUri": { + "description": "RedirectURI is the callback URL where the upstream IDP will redirect after authentication.\nWhen not specified, defaults to `{resourceUrl}/oauth/callback` where `resourceUrl` is the\nURL associated with the resource (e.g., MCPServer or vMCP) using this config.", + "type": "string" + }, + "scopes": { + "description": "Scopes are the OAuth scopes to request from the upstream IDP.\nIf not specified, defaults to [\"openid\", \"offline_access\"].\nWhen using additionalAuthorizationParams with provider-specific refresh token\nmechanisms (e.g., Google's access_type=offline), set explicit scopes to avoid\nsending both offline_access and the provider-specific parameter.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "userInfoOverride": { + "description": "UserInfoOverride allows customizing UserInfo fetching behavior for OIDC providers.\nBy default, the UserInfo endpoint is discovered automatically via OIDC discovery.\nUse this to override the endpoint URL, HTTP method, or field mappings for providers\nthat return non-standard claim names in their UserInfo response.", + "properties": { + "additionalHeaders": { + "additionalProperties": { + "type": "string" + }, + "description": "AdditionalHeaders contains extra headers to include in the userinfo request.\nUseful for providers that require specific headers (e.g., GitHub's Accept header).", + "type": "object" + }, + "endpointUrl": { + "description": "EndpointURL is the URL of the userinfo endpoint.", + "pattern": "^https?://.*$", + "type": "string" + }, + "fieldMapping": { + "description": "FieldMapping contains custom field mapping configuration for non-standard providers.\nIf nil, standard OIDC field names are used (\"sub\", \"name\", \"email\").", + "properties": { + "emailFields": { + "description": "EmailFields is an ordered list of field names to try for the email address.\nThe first non-empty value found will be used.\nDefault: [\"email\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "nameFields": { + "description": "NameFields is an ordered list of field names to try for the display name.\nThe first non-empty value found will be used.\nDefault: [\"name\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "subjectFields": { + "description": "SubjectFields is an ordered list of field names to try for the user ID.\nThe first non-empty value found will be used.\nDefault: [\"sub\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "type": "object", + "additionalProperties": false + }, + "httpMethod": { + "description": "HTTPMethod is the HTTP method to use for the userinfo request.\nIf not specified, defaults to GET.", + "enum": [ + "GET", + "POST" + ], + "type": "string" + } + }, + "required": [ + "endpointUrl" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "clientId", + "issuerUrl" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type specifies the provider type: \"oidc\" or \"oauth2\"", + "enum": [ + "oidc", + "oauth2" + ], + "type": "string" + } + }, + "required": [ + "name", + "type" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "type must be 'oidc' or 'oauth2'; oidcConfig must be set when type is 'oidc' and oauth2Config must be set when type is 'oauth2' (and the other must not be set)", + "rule": "self.type == 'oidc' ? (has(self.oidcConfig) && !has(self.oauth2Config)) : self.type == 'oauth2' ? (has(self.oauth2Config) && !has(self.oidcConfig)) : false" + } + ], + "additionalProperties": false + }, + "minItems": 1, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "required": [ + "issuer", + "upstreamProviders" + ], + "type": "object", + "additionalProperties": false + }, + "config": { + "description": "Config is the Virtual MCP server configuration.\nThe audit config from here is also supported, but not required.", + "properties": { + "aggregation": { + "description": "Aggregation defines tool aggregation and conflict resolution strategies.\nSupports ToolConfigRef for Kubernetes-native MCPToolConfig resource references.", + "properties": { + "conflictResolution": { + "default": "prefix", + "description": "ConflictResolution defines the strategy for resolving tool name conflicts.\n- prefix: Automatically prefix tool names with workload identifier\n- priority: First workload in priority order wins\n- manual: Explicitly define overrides for all conflicts", + "enum": [ + "prefix", + "priority", + "manual" + ], + "type": "string" + }, + "conflictResolutionConfig": { + "description": "ConflictResolutionConfig provides configuration for the chosen strategy.", + "properties": { + "prefixFormat": { + "default": "{workload}_", + "description": "PrefixFormat defines the prefix format for the \"prefix\" strategy.\nSupports placeholders: {workload}, {workload}_, {workload}.", + "type": "string" + }, + "priorityOrder": { + "description": "PriorityOrder defines the workload priority order for the \"priority\" strategy.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "additionalProperties": false + }, + "excludeAllTools": { + "description": "ExcludeAllTools hides all backend tools from MCP clients when true.\nHidden tools are NOT advertised in tools/list responses, but they ARE\navailable in the routing table for composite tools to use.\nThis enables the use case where you want to hide raw backend tools from\ndirect client access while exposing curated composite tool workflows.", + "type": "boolean" + }, + "tools": { + "description": "Tools defines per-workload tool filtering and overrides.", + "items": { + "description": "WorkloadToolConfig defines tool filtering and overrides for a specific workload.", + "properties": { + "excludeAll": { + "description": "ExcludeAll hides all tools from this workload from MCP clients when true.\nHidden tools are NOT advertised in tools/list responses, but they ARE\navailable in the routing table for composite tools to use.\nThis enables the use case where you want to hide raw backend tools from\ndirect client access while exposing curated composite tool workflows.", + "type": "boolean" + }, + "filter": { + "description": "Filter is an allow-list of tool names to advertise to MCP clients.\nTools NOT in this list are hidden from clients (not in tools/list response)\nbut remain available in the routing table for composite tools to use.\nThis enables selective exposure of backend tools while allowing composite\nworkflows to orchestrate all backend capabilities.\nOnly used if ToolConfigRef is not specified.", + "items": { + "type": "string" + }, + "type": "array" + }, + "overrides": { + "additionalProperties": { + "description": "ToolOverride defines tool name, description, and annotation overrides.", + "properties": { + "annotations": { + "description": "Annotations overrides specific tool annotation fields.\nOnly specified fields are overridden; others pass through from the backend.", + "properties": { + "destructiveHint": { + "description": "DestructiveHint overrides the destructive hint annotation.", + "type": "boolean" + }, + "idempotentHint": { + "description": "IdempotentHint overrides the idempotent hint annotation.", + "type": "boolean" + }, + "openWorldHint": { + "description": "OpenWorldHint overrides the open-world hint annotation.", + "type": "boolean" + }, + "readOnlyHint": { + "description": "ReadOnlyHint overrides the read-only hint annotation.", + "type": "boolean" + }, + "title": { + "description": "Title overrides the human-readable title annotation.", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "description": { + "description": "Description is the new tool description.", + "type": "string" + }, + "name": { + "description": "Name is the new tool name (for renaming).", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "description": "Overrides is an inline map of tool overrides for renaming and description changes.\nOverrides are applied to tools before conflict resolution and affect both\nadvertising and routing (the overridden name is used everywhere).\nOnly used if ToolConfigRef is not specified.", + "type": "object" + }, + "toolConfigRef": { + "description": "ToolConfigRef references an MCPToolConfig resource for tool filtering and renaming.\nIf specified, Filter and Overrides are ignored.\nOnly used when running in Kubernetes with the operator.", + "properties": { + "name": { + "description": "Name is the name of the MCPToolConfig resource in the same namespace.", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "workload": { + "description": "Workload is the name of the backend MCPServer workload.", + "type": "string" + } + }, + "required": [ + "workload" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + } + }, + "type": "object", + "additionalProperties": false + }, + "audit": { + "description": "Audit configures audit logging for the Virtual MCP server.\nWhen present, audit logs include MCP protocol operations.\nSee audit.Config for available configuration options.", + "properties": { + "component": { + "description": "Component is the component name to use in audit events.", + "type": "string" + }, + "detectApplicationErrors": { + "default": true, + "description": "DetectApplicationErrors controls whether the audit middleware inspects\nJSON-RPC response bodies for application-level errors when the HTTP\nstatus code indicates success (2xx). When enabled, a small prefix of\nthe response body is buffered to detect JSON-RPC error fields,\nindependent of the IncludeResponseData setting.", + "type": "boolean" + }, + "enabled": { + "default": false, + "description": "Enabled controls whether audit logging is enabled.\nWhen true, enables audit logging with the configured options.", + "type": "boolean" + }, + "eventTypes": { + "description": "EventTypes specifies which event types to audit. If empty, all events are audited.", + "items": { + "type": "string" + }, + "type": "array" + }, + "excludeEventTypes": { + "description": "ExcludeEventTypes specifies which event types to exclude from auditing.\nThis takes precedence over EventTypes.", + "items": { + "type": "string" + }, + "type": "array" + }, + "includeRequestData": { + "default": false, + "description": "IncludeRequestData determines whether to include request data in audit logs.", + "type": "boolean" + }, + "includeResponseData": { + "default": false, + "description": "IncludeResponseData determines whether to include response data in audit logs.", + "type": "boolean" + }, + "logFile": { + "description": "LogFile specifies the file path for audit logs. If empty, logs to stdout.", + "type": "string" + }, + "maxDataSize": { + "default": 1024, + "description": "MaxDataSize limits the size of request/response data included in audit logs (in bytes).", + "type": "integer" + } + }, + "type": "object", + "additionalProperties": false + }, + "backends": { + "description": "Backends defines pre-configured backend servers for static mode.\nWhen OutgoingAuth.Source is \"inline\", this field contains the full list of backend\nservers with their URLs and transport types, eliminating the need for K8s API access.\nWhen OutgoingAuth.Source is \"discovered\", this field is empty and backends are\ndiscovered at runtime via Kubernetes API.", + "items": { + "description": "StaticBackendConfig defines a pre-configured backend server for static mode.\nThis allows vMCP to operate without Kubernetes API access by embedding all backend\ninformation directly in the configuration.", + "properties": { + "caBundlePath": { + "description": "CABundlePath is the file path to a custom CA certificate bundle for TLS verification.\nOnly valid when Type is \"entry\". The operator mounts CA bundles at\n/etc/toolhive/ca-bundles//ca.crt.", + "type": "string" + }, + "metadata": { + "additionalProperties": { + "type": "string" + }, + "description": "Metadata is a custom key-value map for storing additional backend information\nsuch as labels, tags, or other arbitrary data (e.g., \"env\": \"prod\", \"region\": \"us-east-1\").\nThis is NOT Kubernetes ObjectMeta - it's a simple string map for user-defined metadata.\nReserved keys: \"group\" is automatically set by vMCP and any user-provided value will be overridden.", + "type": "object" + }, + "name": { + "description": "Name is the backend identifier.\nMust match the backend name from the MCPGroup for auth config resolution.", + "type": "string" + }, + "transport": { + "description": "Transport is the MCP transport protocol: \"sse\" or \"streamable-http\"\nOnly network transports supported by vMCP client are allowed.", + "enum": [ + "sse", + "streamable-http" + ], + "type": "string" + }, + "type": { + "description": "Type is the backend workload type: \"entry\" for MCPServerEntry backends, or empty\nfor container/proxy backends. Entry backends connect directly to remote MCP servers.", + "enum": [ + "entry", + "" + ], + "type": "string" + }, + "url": { + "description": "URL is the backend's MCP server base URL.", + "pattern": "^https?://", + "type": "string" + } + }, + "required": [ + "name", + "transport", + "url" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + }, + "compositeToolRefs": { + "description": "CompositeToolRefs references VirtualMCPCompositeToolDefinition resources\nfor complex, reusable workflows. Only applicable when running in Kubernetes.\nReferenced resources must be in the same namespace as the VirtualMCPServer.", + "items": { + "description": "CompositeToolRef defines a reference to a VirtualMCPCompositeToolDefinition resource.\nThe referenced resource must be in the same namespace as the VirtualMCPServer.", + "properties": { + "name": { + "description": "Name is the name of the VirtualMCPCompositeToolDefinition resource in the same namespace.", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + }, + "compositeTools": { + "description": "CompositeTools defines inline composite tool workflows.\nFull workflow definitions are embedded in the configuration.\nFor Kubernetes, complex workflows can also reference VirtualMCPCompositeToolDefinition CRDs.", + "items": { + "description": "CompositeToolConfig defines a composite tool workflow.\nThis matches the YAML structure from the proposal (lines 173-255).", + "properties": { + "description": { + "description": "Description describes what the workflow does.", + "type": "string" + }, + "name": { + "description": "Name is the workflow name (unique identifier).", + "type": "string" + }, + "output": { + "description": "Output defines the structured output schema for this workflow.\nIf not specified, the workflow returns the last step's output (backward compatible).", + "properties": { + "properties": { + "additionalProperties": { + "description": "OutputProperty defines a single output property.\nFor non-object types, Value is required.\nFor object types, either Value or Properties must be specified (but not both).", + "properties": { + "default": { + "description": "Default is the fallback value if template expansion fails.\nType coercion is applied to match the declared Type.", + "x-kubernetes-preserve-unknown-fields": true + }, + "description": { + "description": "Description is a human-readable description exposed to clients and models", + "type": "string" + }, + "properties": { + "description": "Properties defines nested properties for object types.\nEach nested property has full metadata (type, description, value/properties).", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "type": { + "description": "Type is the JSON Schema type: \"string\", \"integer\", \"number\", \"boolean\", \"object\", \"array\"", + "enum": [ + "string", + "integer", + "number", + "boolean", + "object", + "array" + ], + "type": "string" + }, + "value": { + "description": "Value is a template string for constructing the runtime value.\nFor object types, this can be a JSON string that will be deserialized.\nSupports template syntax: {{.steps.step_id.output.field}}, {{.params.param_name}}", + "type": "string" + }, + "additionalProperties": false + }, + "required": [ + "type" + ], + "type": "object", + "additionalProperties": false + }, + "description": "Properties defines the output properties.\nMap key is the property name, value is the property definition.", + "type": "object" + }, + "required": { + "description": "Required lists property names that must be present in the output.", + "items": { + "type": "string" + }, + "type": "array" + }, + "additionalProperties": false + }, + "required": [ + "properties" + ], + "type": "object", + "additionalProperties": false + }, + "parameters": { + "description": "Parameters defines input parameter schema in JSON Schema format.\nShould be a JSON Schema object with \"type\": \"object\" and \"properties\".\nExample:\n {\n \"type\": \"object\",\n \"properties\": {\n \"param1\": {\"type\": \"string\", \"default\": \"value\"},\n \"param2\": {\"type\": \"integer\"}\n },\n \"required\": [\"param2\"]\n }\n\nWe use json.Map rather than a typed struct because JSON Schema is highly\nflexible with many optional fields (default, enum, minimum, maximum, pattern,\nitems, additionalProperties, oneOf, anyOf, allOf, etc.). Using json.Map\nallows full JSON Schema compatibility without needing to define every possible\nfield, and matches how the MCP SDK handles inputSchema.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "steps": { + "description": "Steps are the workflow steps to execute.", + "items": { + "description": "WorkflowStepConfig defines a single workflow step.\nThis matches the proposal's step configuration (lines 180-255).", + "properties": { + "arguments": { + "description": "Arguments is a map of argument values with template expansion support.\nSupports Go template syntax with .params and .steps for string values.\nNon-string values (integers, booleans, arrays, objects) are passed as-is.\nNote: the templating is only supported on the first level of the key-value pairs.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "collection": { + "description": "Collection is a Go template expression that resolves to a JSON array or a slice.\nOnly used when Type is \"forEach\".", + "type": "string" + }, + "condition": { + "description": "Condition is a template expression that determines if the step should execute", + "type": "string" + }, + "defaultResults": { + "description": "DefaultResults provides fallback output values when this step is skipped\n(due to condition evaluating to false) or fails (when onError.action is \"continue\").\nEach key corresponds to an output field name referenced by downstream steps.\nRequired if the step may be skipped AND downstream steps reference this step's output.", + "x-kubernetes-preserve-unknown-fields": true + }, + "dependsOn": { + "description": "DependsOn lists step IDs that must complete before this step", + "items": { + "type": "string" + }, + "type": "array" + }, + "id": { + "description": "ID is the unique identifier for this step.", + "type": "string" + }, + "itemVar": { + "description": "ItemVar is the variable name used to reference the current item in forEach templates.\nDefaults to \"item\" if not specified.\nOnly used when Type is \"forEach\".", + "type": "string" + }, + "maxIterations": { + "description": "MaxIterations limits the number of items that can be iterated over.\nDefaults to 100, hard cap at 1000.\nOnly used when Type is \"forEach\".", + "type": "integer" + }, + "maxParallel": { + "description": "MaxParallel limits the number of concurrent iterations in a forEach step.\nDefaults to the DAG executor's maxParallel (10).\nOnly used when Type is \"forEach\".", + "type": "integer" + }, + "message": { + "description": "Message is the elicitation message\nOnly used when Type is \"elicitation\"", + "type": "string" + }, + "onCancel": { + "description": "OnCancel defines the action to take when the user cancels/dismisses the elicitation\nOnly used when Type is \"elicitation\"", + "properties": { + "action": { + "default": "abort", + "description": "Action defines the action to take when the user declines or cancels\n- skip_remaining: Skip remaining steps in the workflow\n- abort: Abort the entire workflow execution\n- continue: Continue to the next step", + "enum": [ + "skip_remaining", + "abort", + "continue" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "onDecline": { + "description": "OnDecline defines the action to take when the user explicitly declines the elicitation\nOnly used when Type is \"elicitation\"", + "properties": { + "action": { + "default": "abort", + "description": "Action defines the action to take when the user declines or cancels\n- skip_remaining: Skip remaining steps in the workflow\n- abort: Abort the entire workflow execution\n- continue: Continue to the next step", + "enum": [ + "skip_remaining", + "abort", + "continue" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "onError": { + "description": "OnError defines error handling behavior", + "properties": { + "action": { + "default": "abort", + "description": "Action defines the action to take on error", + "enum": [ + "abort", + "continue", + "retry" + ], + "type": "string" + }, + "retryCount": { + "description": "RetryCount is the maximum number of retries\nOnly used when Action is \"retry\"", + "type": "integer" + }, + "retryDelay": { + "description": "RetryDelay is the delay between retry attempts\nOnly used when Action is \"retry\"", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "schema": { + "description": "Schema defines the expected response schema for elicitation", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "step": { + "description": "InnerStep defines the step to execute for each item in the collection.\nOnly used when Type is \"forEach\". Only tool-type inner steps are supported.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "timeout": { + "description": "Timeout is the maximum execution time for this step", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "tool": { + "description": "Tool is the tool to call (format: \"workload.tool_name\")\nOnly used when Type is \"tool\"", + "type": "string" + }, + "type": { + "default": "tool", + "description": "Type is the step type (tool, elicitation, etc.)", + "enum": [ + "tool", + "elicitation", + "forEach" + ], + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + }, + "timeout": { + "description": "Timeout is the maximum workflow execution time.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + } + }, + "required": [ + "name", + "steps" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + }, + "groupRef": { + "description": "Group references an existing MCPGroup that defines backend workloads.\nIn standalone CLI mode, this is set from the YAML config file.\nIn Kubernetes, the operator populates this from spec.groupRef during conversion.", + "type": "string" + }, + "incomingAuth": { + "description": "IncomingAuth configures how clients authenticate to the virtual MCP server.\nWhen using the Kubernetes operator, this is populated by the converter from\nVirtualMCPServerSpec.IncomingAuth and any values set here will be superseded.", + "properties": { + "authz": { + "description": "Authz contains authorization configuration (optional).", + "properties": { + "entitiesJson": { + "description": "EntitiesJSON is a JSON string representing Cedar entities. Required for\nenterprise policies that rely on transitive relationships (e.g.\n`ClaimGroup \u2192 PlatformRole`) \u2014 without it the Cedar authorizer is\nconstructed with an empty entity store and `in` checks against absent\nentities silently evaluate to false. Defaults to \"[]\" when empty.", + "type": "string" + }, + "groupClaimName": { + "description": "GroupClaimName is the JWT claim key that contains group membership for\nthe principal. When set, takes priority over the well-known defaults\n(\"groups\", \"roles\", \"cognito:groups\"). Use this for IDPs that place\ngroups under a URI-style claim (e.g. \"https://example.com/groups\").\nWhen empty, only the well-known claim names are checked.", + "type": "string" + }, + "groupEntityType": { + "description": "GroupEntityType is the Cedar entity type name used for principal parent\nUIDs synthesised from JWT group/role claims. Defaults to \"THVGroup\" when\nempty. Must match the entity type used in EntitiesJSON for transitive\n`in` checks to resolve. Namespaced names (`Foo::Bar`) are not yet supported.", + "type": "string" + }, + "policies": { + "description": "Policies contains Cedar policy definitions (when Type = \"cedar\").", + "items": { + "type": "string" + }, + "type": "array" + }, + "primaryUpstreamProvider": { + "description": "PrimaryUpstreamProvider names the upstream IDP provider whose access\ntoken should be used as the source of JWT claims for Cedar evaluation.\nWhen empty, claims from the ToolHive-issued token are used.\nMust match an upstream provider name configured in the embedded auth server\n(e.g. \"default\", \"github\"). Only relevant when the embedded auth server is active.", + "type": "string" + }, + "roleClaimName": { + "description": "RoleClaimName is the JWT claim key that contains role membership for the\nprincipal. When set, the claim is extracted separately from GroupClaimName\nand both are mapped to the configured group entity type. When empty, no\nrole extraction is performed.", + "type": "string" + }, + "type": { + "description": "Type is the authz type: \"cedar\", \"none\"", + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "additionalProperties": false + }, + "oidc": { + "description": "OIDC contains OIDC configuration (when Type = \"oidc\").", + "properties": { + "audience": { + "description": "Audience is the required token audience.", + "type": "string" + }, + "clientId": { + "description": "ClientID is the OAuth client ID.", + "type": "string" + }, + "clientSecretEnv": { + "description": "ClientSecretEnv is the name of the environment variable containing the client secret.\nThis is the secure way to reference secrets - the actual secret value is never stored\nin configuration files, only the environment variable name.\nThe secret value will be resolved from this environment variable at runtime.", + "type": "string" + }, + "insecureAllowHttp": { + "description": "InsecureAllowHTTP allows HTTP (non-HTTPS) OIDC issuers for development/testing\nWARNING: This is insecure and should NEVER be used in production", + "type": "boolean" + }, + "introspectionUrl": { + "description": "IntrospectionURL is the token introspection endpoint URL (RFC 7662).\nWhen set, enables token introspection for opaque (non-JWT) tokens.", + "type": "string" + }, + "issuer": { + "description": "Issuer is the OIDC issuer URL.", + "pattern": "^https?://", + "type": "string" + }, + "jwksAllowPrivateIp": { + "description": "JwksAllowPrivateIP allows OIDC discovery and JWKS fetches to private IP addresses.\nEnable when the embedded auth server runs on a loopback address and\nthe OIDC middleware needs to fetch its JWKS from that address.\nUse with caution - only enable for trusted internal IDPs or testing.", + "type": "boolean" + }, + "jwksUrl": { + "description": "JWKSURL is the explicit JWKS endpoint URL.\nWhen set, skips OIDC discovery and fetches the JWKS directly from this URL.\nThis is useful when the OIDC issuer does not serve a /.well-known/openid-configuration.", + "type": "string" + }, + "protectedResourceAllowPrivateIp": { + "description": "ProtectedResourceAllowPrivateIP allows protected resource endpoint on private IP addresses\nUse with caution - only enable for trusted internal IDPs or testing", + "type": "boolean" + }, + "resource": { + "description": "Resource is the OAuth 2.0 resource indicator (RFC 8707).\nUsed in WWW-Authenticate header and OAuth discovery metadata (RFC 9728).\nIf not specified, defaults to Audience.", + "type": "string" + }, + "scopes": { + "description": "Scopes are the required OAuth scopes.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "audience", + "clientId", + "issuer" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type is the auth type: \"oidc\", \"local\", \"anonymous\"", + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "additionalProperties": false + }, + "metadata": { + "additionalProperties": { + "type": "string" + }, + "description": "Metadata stores additional configuration metadata.", + "type": "object" + }, + "name": { + "description": "Name is the virtual MCP server name.", + "type": "string" + }, + "operational": { + "description": "Operational configures operational settings.", + "properties": { + "failureHandling": { + "description": "FailureHandling configures failure handling behavior.", + "properties": { + "circuitBreaker": { + "description": "CircuitBreaker configures circuit breaker behavior.", + "properties": { + "enabled": { + "default": false, + "description": "Enabled controls whether circuit breaker is enabled.", + "type": "boolean" + }, + "failureThreshold": { + "default": 5, + "description": "FailureThreshold is the number of failures before opening the circuit.\nMust be >= 1.", + "minimum": 1, + "type": "integer" + }, + "timeout": { + "default": "60s", + "description": "Timeout is the duration to wait before attempting to close the circuit.\nMust be >= 1s to prevent thrashing.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string", + "x-kubernetes-validations": [ + { + "message": "timeout must be >= 1s", + "rule": "self == '' || duration(self) >= duration('1s')" + } + ] + } + }, + "type": "object", + "additionalProperties": false + }, + "healthCheckInterval": { + "default": "30s", + "description": "HealthCheckInterval is the interval between health checks.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "healthCheckTimeout": { + "default": "10s", + "description": "HealthCheckTimeout is the maximum duration for a single health check operation.\nShould be less than HealthCheckInterval to prevent checks from queuing up.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "partialFailureMode": { + "default": "fail", + "description": "PartialFailureMode defines behavior when some backends are unavailable.\n- fail: Fail entire request if any backend is unavailable\n- best_effort: Continue with available backends", + "enum": [ + "fail", + "best_effort" + ], + "type": "string" + }, + "statusReportingInterval": { + "default": "30s", + "description": "StatusReportingInterval is the interval for reporting status updates to Kubernetes.\nThis controls how often the vMCP runtime reports backend health and phase changes.\nLower values provide faster status updates but increase API server load.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "unhealthyThreshold": { + "default": 3, + "description": "UnhealthyThreshold is the number of consecutive failures before marking unhealthy.", + "type": "integer" + } + }, + "type": "object", + "additionalProperties": false + }, + "logLevel": { + "description": "LogLevel sets the logging level for the Virtual MCP server.\nThe only valid value is \"debug\" to enable debug logging.\nWhen omitted or empty, the server uses info level logging.", + "enum": [ + "debug" + ], + "type": "string" + }, + "timeouts": { + "description": "Timeouts configures timeout settings.", + "properties": { + "default": { + "default": "30s", + "description": "Default is the default timeout for backend requests.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "perWorkload": { + "additionalProperties": { + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "description": "PerWorkload defines per-workload timeout overrides.", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "optimizer": { + "description": "Optimizer configures the MCP optimizer for context optimization on large toolsets.\nWhen enabled, vMCP exposes only find_tool and call_tool operations to clients\ninstead of all backend tools directly. This reduces token usage by allowing\nLLMs to discover relevant tools on demand rather than receiving all tool definitions.", + "properties": { + "embeddingService": { + "description": "EmbeddingService is the full base URL of the embedding service endpoint\n(e.g., http://my-embedding.default.svc.cluster.local:8080) for semantic\ntool discovery.\n\nIn a Kubernetes environment, it is more convenient to use the\nVirtualMCPServerSpec.EmbeddingServerRef field instead of setting this\ndirectly. EmbeddingServerRef references an EmbeddingServer CRD by name,\nand the operator automatically resolves the referenced resource's\nStatus.URL to populate this field. This provides managed lifecycle\n(the operator watches the EmbeddingServer for readiness and URL changes)\nand avoids hardcoding service URLs in the config. If both\nEmbeddingServerRef and this field are set, EmbeddingServerRef takes\nprecedence and this value is overridden with a warning.", + "type": "string" + }, + "embeddingServiceTimeout": { + "default": "30s", + "description": "EmbeddingServiceTimeout is the HTTP request timeout for calls to the embedding service.\nDefaults to 30s if not specified.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "hybridSearchSemanticRatio": { + "description": "HybridSearchSemanticRatio controls the balance between semantic (meaning-based)\nand keyword search results. 0.0 = all keyword, 1.0 = all semantic.\nDefaults to \"0.5\" if not specified or empty.\nSerialized as a string because CRDs do not support float types portably.", + "pattern": "^([0-9]*[.])?[0-9]+$", + "type": "string" + }, + "maxToolsToReturn": { + "description": "MaxToolsToReturn is the maximum number of tool results returned by a search query.\nDefaults to 8 if not specified or zero.", + "maximum": 50, + "minimum": 1, + "type": "integer" + }, + "semanticDistanceThreshold": { + "description": "SemanticDistanceThreshold is the maximum distance for semantic search results.\nResults exceeding this threshold are filtered out from semantic search.\nThis threshold does not apply to keyword search.\nRange: 0 = identical, 2 = completely unrelated.\nDefaults to \"1.0\" if not specified or empty.\nSerialized as a string because CRDs do not support float types portably.", + "pattern": "^([0-9]*[.])?[0-9]+$", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "outgoingAuth": { + "description": "OutgoingAuth configures how the virtual MCP server authenticates to backends.\nWhen using the Kubernetes operator, this is populated by the converter from\nVirtualMCPServerSpec.OutgoingAuth and any values set here will be superseded.", + "properties": { + "backends": { + "additionalProperties": { + "description": "BackendAuthStrategy defines how to authenticate to a specific backend.\n\nThis struct provides type-safe configuration for different authentication strategies\nusing HeaderInjection or TokenExchange fields based on the Type field.", + "properties": { + "awsSts": { + "description": "AwsSts contains configuration for AWS STS auth strategy.\nUsed when Type = \"aws_sts\".", + "properties": { + "fallbackRoleArn": { + "description": "FallbackRoleArn is the IAM role ARN to assume when no role mappings match.", + "type": "string" + }, + "region": { + "description": "Region is the AWS region for the STS endpoint and service.", + "type": "string" + }, + "roleClaim": { + "description": "RoleClaim is the JWT claim to use for role mapping evaluation.", + "type": "string" + }, + "roleMappings": { + "description": "RoleMappings defines claim-based role selection rules.", + "items": { + "description": "RoleMapping defines a rule for mapping JWT claims to IAM roles.\nMappings are evaluated in priority order (lower number = higher priority).", + "properties": { + "claim": { + "description": "Claim is a simple claim value to match against the RoleClaim field.", + "type": "string" + }, + "matcher": { + "description": "Matcher is a CEL expression for complex matching against JWT claims.", + "type": "string" + }, + "priority": { + "description": "Priority determines evaluation order (lower values = higher priority).\nMirrors awssts.RoleMapping.Priority, which is *int because the role mapper\nuses math.MaxInt for nil-priority semantics in effectivePriority.", + "type": "integer" + }, + "roleArn": { + "description": "RoleArn is the IAM role ARN to assume when this mapping matches.", + "type": "string" + } + }, + "required": [ + "roleArn" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "service": { + "description": "Service is the AWS service name for SigV4 signing.", + "type": "string" + }, + "sessionDuration": { + "description": "SessionDuration is the duration in seconds for the STS session.", + "format": "int32", + "type": "integer" + }, + "sessionNameClaim": { + "description": "SessionNameClaim is the JWT claim to use for the role session name.", + "type": "string" + }, + "subjectProviderName": { + "description": "SubjectProviderName selects which upstream provider's token to use as the\nweb identity token for AssumeRoleWithWebIdentity. When set, the token is\nlooked up from Identity.UpstreamTokens instead of the request's\nAuthorization header.", + "type": "string" + } + }, + "required": [ + "region" + ], + "type": "object", + "additionalProperties": false + }, + "headerInjection": { + "description": "HeaderInjection contains configuration for header injection auth strategy.\nUsed when Type = \"header_injection\".", + "properties": { + "headerName": { + "description": "HeaderName is the name of the header to inject (e.g., \"Authorization\").", + "type": "string" + }, + "headerValue": { + "description": "HeaderValue is the static header value to inject.\nEither HeaderValue or HeaderValueEnv should be set, not both.", + "type": "string" + }, + "headerValueEnv": { + "description": "HeaderValueEnv is the environment variable name containing the header value.\nThe value will be resolved at runtime from this environment variable.\nEither HeaderValue or HeaderValueEnv should be set, not both.", + "type": "string" + } + }, + "required": [ + "headerName" + ], + "type": "object", + "additionalProperties": false + }, + "tokenExchange": { + "description": "TokenExchange contains configuration for token exchange auth strategy.\nUsed when Type = \"token_exchange\".", + "properties": { + "audience": { + "description": "Audience is the target audience for the exchanged token.", + "type": "string" + }, + "clientId": { + "description": "ClientID is the OAuth client ID for the token exchange request.", + "type": "string" + }, + "clientSecret": { + "description": "ClientSecret is the OAuth client secret (use ClientSecretEnv for security).", + "type": "string" + }, + "clientSecretEnv": { + "description": "ClientSecretEnv is the environment variable name containing the client secret.\nThe value will be resolved at runtime from this environment variable.", + "type": "string" + }, + "scopes": { + "description": "Scopes are the requested scopes for the exchanged token.", + "items": { + "type": "string" + }, + "type": "array" + }, + "subjectProviderName": { + "description": "SubjectProviderName selects which upstream provider's token to use as the\nsubject token. When set, the token is looked up from Identity.UpstreamTokens\ninstead of using Identity.Token.\nWhen left empty and an embedded authorization server is configured, the system\nautomatically populates this field with the first configured upstream provider name.\nSet it explicitly to override that default or to select a specific provider when\nmultiple upstreams are configured.", + "type": "string" + }, + "subjectTokenType": { + "description": "SubjectTokenType is the token type of the incoming subject token.\nDefaults to \"urn:ietf:params:oauth:token-type:access_token\" if not specified.", + "type": "string" + }, + "tokenUrl": { + "description": "TokenURL is the OAuth token endpoint URL for token exchange.", + "type": "string" + } + }, + "required": [ + "tokenUrl" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type is the auth strategy: \"unauthenticated\", \"header_injection\", \"token_exchange\", \"upstream_inject\", \"aws_sts\", \"obo\"", + "type": "string" + }, + "upstreamInject": { + "description": "UpstreamInject contains configuration for upstream inject auth strategy.\nUsed when Type = \"upstream_inject\".", + "properties": { + "providerName": { + "description": "ProviderName is the name of the upstream provider configured in the\nembedded authorization server. Must match an entry in AuthServer.Upstreams.", + "type": "string" + } + }, + "required": [ + "providerName" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "type" + ], + "type": "object", + "additionalProperties": false + }, + "description": "Backends contains per-backend auth configuration.", + "type": "object" + }, + "default": { + "description": "Default is the default auth strategy for backends without explicit config.", + "properties": { + "awsSts": { + "description": "AwsSts contains configuration for AWS STS auth strategy.\nUsed when Type = \"aws_sts\".", + "properties": { + "fallbackRoleArn": { + "description": "FallbackRoleArn is the IAM role ARN to assume when no role mappings match.", + "type": "string" + }, + "region": { + "description": "Region is the AWS region for the STS endpoint and service.", + "type": "string" + }, + "roleClaim": { + "description": "RoleClaim is the JWT claim to use for role mapping evaluation.", + "type": "string" + }, + "roleMappings": { + "description": "RoleMappings defines claim-based role selection rules.", + "items": { + "description": "RoleMapping defines a rule for mapping JWT claims to IAM roles.\nMappings are evaluated in priority order (lower number = higher priority).", + "properties": { + "claim": { + "description": "Claim is a simple claim value to match against the RoleClaim field.", + "type": "string" + }, + "matcher": { + "description": "Matcher is a CEL expression for complex matching against JWT claims.", + "type": "string" + }, + "priority": { + "description": "Priority determines evaluation order (lower values = higher priority).\nMirrors awssts.RoleMapping.Priority, which is *int because the role mapper\nuses math.MaxInt for nil-priority semantics in effectivePriority.", + "type": "integer" + }, + "roleArn": { + "description": "RoleArn is the IAM role ARN to assume when this mapping matches.", + "type": "string" + } + }, + "required": [ + "roleArn" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "service": { + "description": "Service is the AWS service name for SigV4 signing.", + "type": "string" + }, + "sessionDuration": { + "description": "SessionDuration is the duration in seconds for the STS session.", + "format": "int32", + "type": "integer" + }, + "sessionNameClaim": { + "description": "SessionNameClaim is the JWT claim to use for the role session name.", + "type": "string" + }, + "subjectProviderName": { + "description": "SubjectProviderName selects which upstream provider's token to use as the\nweb identity token for AssumeRoleWithWebIdentity. When set, the token is\nlooked up from Identity.UpstreamTokens instead of the request's\nAuthorization header.", + "type": "string" + } + }, + "required": [ + "region" + ], + "type": "object", + "additionalProperties": false + }, + "headerInjection": { + "description": "HeaderInjection contains configuration for header injection auth strategy.\nUsed when Type = \"header_injection\".", + "properties": { + "headerName": { + "description": "HeaderName is the name of the header to inject (e.g., \"Authorization\").", + "type": "string" + }, + "headerValue": { + "description": "HeaderValue is the static header value to inject.\nEither HeaderValue or HeaderValueEnv should be set, not both.", + "type": "string" + }, + "headerValueEnv": { + "description": "HeaderValueEnv is the environment variable name containing the header value.\nThe value will be resolved at runtime from this environment variable.\nEither HeaderValue or HeaderValueEnv should be set, not both.", + "type": "string" + } + }, + "required": [ + "headerName" + ], + "type": "object", + "additionalProperties": false + }, + "tokenExchange": { + "description": "TokenExchange contains configuration for token exchange auth strategy.\nUsed when Type = \"token_exchange\".", + "properties": { + "audience": { + "description": "Audience is the target audience for the exchanged token.", + "type": "string" + }, + "clientId": { + "description": "ClientID is the OAuth client ID for the token exchange request.", + "type": "string" + }, + "clientSecret": { + "description": "ClientSecret is the OAuth client secret (use ClientSecretEnv for security).", + "type": "string" + }, + "clientSecretEnv": { + "description": "ClientSecretEnv is the environment variable name containing the client secret.\nThe value will be resolved at runtime from this environment variable.", + "type": "string" + }, + "scopes": { + "description": "Scopes are the requested scopes for the exchanged token.", + "items": { + "type": "string" + }, + "type": "array" + }, + "subjectProviderName": { + "description": "SubjectProviderName selects which upstream provider's token to use as the\nsubject token. When set, the token is looked up from Identity.UpstreamTokens\ninstead of using Identity.Token.\nWhen left empty and an embedded authorization server is configured, the system\nautomatically populates this field with the first configured upstream provider name.\nSet it explicitly to override that default or to select a specific provider when\nmultiple upstreams are configured.", + "type": "string" + }, + "subjectTokenType": { + "description": "SubjectTokenType is the token type of the incoming subject token.\nDefaults to \"urn:ietf:params:oauth:token-type:access_token\" if not specified.", + "type": "string" + }, + "tokenUrl": { + "description": "TokenURL is the OAuth token endpoint URL for token exchange.", + "type": "string" + } + }, + "required": [ + "tokenUrl" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type is the auth strategy: \"unauthenticated\", \"header_injection\", \"token_exchange\", \"upstream_inject\", \"aws_sts\", \"obo\"", + "type": "string" + }, + "upstreamInject": { + "description": "UpstreamInject contains configuration for upstream inject auth strategy.\nUsed when Type = \"upstream_inject\".", + "properties": { + "providerName": { + "description": "ProviderName is the name of the upstream provider configured in the\nembedded authorization server. Must match an entry in AuthServer.Upstreams.", + "type": "string" + } + }, + "required": [ + "providerName" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "type" + ], + "type": "object", + "additionalProperties": false + }, + "source": { + "description": "Source defines how to discover backend auth: \"inline\", \"discovered\"\n- inline: Explicit configuration in OutgoingAuth\n- discovered: Auto-discover from backend MCPServer.externalAuthConfigRef (Kubernetes only)", + "type": "string" + } + }, + "required": [ + "source" + ], + "type": "object", + "additionalProperties": false + }, + "rateLimiting": { + "description": "RateLimiting defines rate limiting configuration for the Virtual MCP server.\nRequires Redis session storage to be configured for distributed rate limiting.", + "properties": { + "perUser": { + "description": "PerUser is a token bucket applied independently to each authenticated user\nat the server level. Requires authentication to be enabled.\nEach unique userID creates Redis keys that expire after 2x refillPeriod.\nMemory formula: unique_users_per_TTL_window * (1 + num_tools_with_per_user_limits) keys.", + "properties": { + "maxTokens": { + "description": "MaxTokens is the maximum number of tokens (bucket capacity).\nThis is also the burst size: the maximum number of requests that can be served\ninstantaneously before the bucket is depleted.", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "refillPeriod": { + "description": "RefillPeriod is the duration to fully refill the bucket from zero to maxTokens.\nThe effective refill rate is maxTokens / refillPeriod tokens per second.\nFormat: Go duration string (e.g., \"1m0s\", \"30s\", \"1h0m0s\").", + "type": "string" + } + }, + "required": [ + "maxTokens", + "refillPeriod" + ], + "type": "object", + "additionalProperties": false + }, + "shared": { + "description": "Shared is a token bucket shared across all users for the entire server.", + "properties": { + "maxTokens": { + "description": "MaxTokens is the maximum number of tokens (bucket capacity).\nThis is also the burst size: the maximum number of requests that can be served\ninstantaneously before the bucket is depleted.", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "refillPeriod": { + "description": "RefillPeriod is the duration to fully refill the bucket from zero to maxTokens.\nThe effective refill rate is maxTokens / refillPeriod tokens per second.\nFormat: Go duration string (e.g., \"1m0s\", \"30s\", \"1h0m0s\").", + "type": "string" + } + }, + "required": [ + "maxTokens", + "refillPeriod" + ], + "type": "object", + "additionalProperties": false + }, + "tools": { + "description": "Tools defines per-tool rate limit overrides.\nEach entry applies additional rate limits to calls targeting a specific tool name.\nA request must pass both the server-level limit and the per-tool limit.", + "items": { + "description": "ToolRateLimitConfig defines rate limits for a specific tool.\nAt least one of shared or perUser must be configured.", + "properties": { + "name": { + "description": "Name is the MCP tool name this limit applies to.", + "minLength": 1, + "type": "string" + }, + "perUser": { + "description": "PerUser token bucket configuration for this tool.", + "properties": { + "maxTokens": { + "description": "MaxTokens is the maximum number of tokens (bucket capacity).\nThis is also the burst size: the maximum number of requests that can be served\ninstantaneously before the bucket is depleted.", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "refillPeriod": { + "description": "RefillPeriod is the duration to fully refill the bucket from zero to maxTokens.\nThe effective refill rate is maxTokens / refillPeriod tokens per second.\nFormat: Go duration string (e.g., \"1m0s\", \"30s\", \"1h0m0s\").", + "type": "string" + } + }, + "required": [ + "maxTokens", + "refillPeriod" + ], + "type": "object", + "additionalProperties": false + }, + "shared": { + "description": "Shared token bucket for this specific tool.", + "properties": { + "maxTokens": { + "description": "MaxTokens is the maximum number of tokens (bucket capacity).\nThis is also the burst size: the maximum number of requests that can be served\ninstantaneously before the bucket is depleted.", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "refillPeriod": { + "description": "RefillPeriod is the duration to fully refill the bucket from zero to maxTokens.\nThe effective refill rate is maxTokens / refillPeriod tokens per second.\nFormat: Go duration string (e.g., \"1m0s\", \"30s\", \"1h0m0s\").", + "type": "string" + } + }, + "required": [ + "maxTokens", + "refillPeriod" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "name" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "at least one of shared or perUser must be configured", + "rule": "has(self.shared) || has(self.perUser)" + } + ], + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "type": "object", + "x-kubernetes-validations": [ + { + "message": "at least one of shared, perUser, or tools must be configured", + "rule": "has(self.shared) || has(self.perUser) || (has(self.tools) && size(self.tools) > 0)" + } + ], + "additionalProperties": false + }, + "sessionStorage": { + "description": "SessionStorage configures session storage for stateful horizontal scaling.\nWhen provider is \"redis\", the operator injects Redis connection parameters\n(address, db, keyPrefix) here. The Redis password is provided separately via\nthe THV_SESSION_REDIS_PASSWORD environment variable.", + "properties": { + "address": { + "description": "Address is the Redis server address (required when provider is redis).", + "type": "string" + }, + "db": { + "default": 0, + "description": "DB is the Redis database number.", + "format": "int32", + "minimum": 0, + "type": "integer" + }, + "keyPrefix": { + "description": "KeyPrefix is an optional prefix for all Redis keys used by ToolHive.", + "type": "string" + }, + "provider": { + "description": "Provider is the session storage backend type.", + "enum": [ + "memory", + "redis" + ], + "type": "string" + } + }, + "required": [ + "provider" + ], + "type": "object", + "additionalProperties": false + }, + "telemetry": { + "description": "Telemetry configures OpenTelemetry-based observability for the Virtual MCP server\nincluding distributed tracing, OTLP metrics export, and Prometheus metrics endpoint.\nDeprecated (Kubernetes operator only): When deploying via the operator, use\nVirtualMCPServer.spec.telemetryConfigRef to reference a shared MCPTelemetryConfig\nresource instead. This field remains valid for standalone (non-operator) deployments.", + "properties": { + "caCertPath": { + "description": "CACertPath is the file path to a CA certificate bundle for the OTLP endpoint.\nWhen set, the OTLP exporters use this CA to verify the collector's TLS certificate\ninstead of relying solely on the system CA pool.", + "type": "string" + }, + "customAttributes": { + "additionalProperties": { + "type": "string" + }, + "description": "CustomAttributes contains custom resource attributes to be added to all telemetry signals.\nThese are parsed from CLI flags (--otel-custom-attributes) or environment variables\n(OTEL_RESOURCE_ATTRIBUTES) as key=value pairs.", + "type": "object" + }, + "enablePrometheusMetricsPath": { + "default": false, + "description": "EnablePrometheusMetricsPath controls whether to expose Prometheus-style /metrics endpoint.\nThe metrics are served on the main transport port at /metrics.\nThis is separate from OTLP metrics which are sent to the Endpoint.", + "type": "boolean" + }, + "endpoint": { + "description": "Endpoint is the OTLP endpoint URL", + "type": "string" + }, + "environmentVariables": { + "description": "EnvironmentVariables is a list of environment variable names that should be\nincluded in telemetry spans as attributes. Only variables in this list will\nbe read from the host machine and included in spans for observability.\nExample: [\"NODE_ENV\", \"DEPLOYMENT_ENV\", \"SERVICE_VERSION\"]", + "items": { + "type": "string" + }, + "type": "array" + }, + "headers": { + "additionalProperties": { + "type": "string" + }, + "description": "Headers contains authentication headers for the OTLP endpoint.", + "type": "object" + }, + "insecure": { + "default": false, + "description": "Insecure indicates whether to use HTTP instead of HTTPS for the OTLP endpoint.", + "type": "boolean" + }, + "metricsEnabled": { + "default": false, + "description": "MetricsEnabled controls whether OTLP metrics are enabled.\nWhen false, OTLP metrics are not sent even if an endpoint is configured.\nThis is independent of EnablePrometheusMetricsPath.", + "type": "boolean" + }, + "samplingRate": { + "default": "0.05", + "description": "SamplingRate is the trace sampling rate (0.0-1.0) as a string.\nOnly used when TracingEnabled is true.\nExample: \"0.05\" for 5% sampling.", + "type": "string" + }, + "serviceName": { + "description": "ServiceName is the service name for telemetry.\nWhen omitted, defaults to the server name (e.g., VirtualMCPServer name).", + "type": "string" + }, + "serviceVersion": { + "description": "ServiceVersion is the service version for telemetry.\nWhen omitted, defaults to the ToolHive version.", + "type": "string" + }, + "tracingEnabled": { + "default": false, + "description": "TracingEnabled controls whether distributed tracing is enabled.\nWhen false, no tracer provider is created even if an endpoint is configured.", + "type": "boolean" + }, + "useLegacyAttributes": { + "default": true, + "description": "UseLegacyAttributes controls whether legacy (pre-MCP OTEL semconv) attribute names\nare emitted alongside the new standard attribute names. When true, spans include both\nold and new attribute names for backward compatibility with existing dashboards.\nCurrently defaults to true; this will change to false in a future release.", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "x-kubernetes-preserve-unknown-fields": true, + "additionalProperties": false + }, + "embeddingServerRef": { + "description": "EmbeddingServerRef references an existing EmbeddingServer resource by name.\nWhen the optimizer is enabled, this field is required to point to a ready EmbeddingServer\nthat provides embedding capabilities.\nThe referenced EmbeddingServer must exist in the same namespace and be ready.", + "properties": { + "name": { + "description": "Name is the name of the EmbeddingServer resource", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "groupRef": { + "description": "GroupRef references the MCPGroup that defines backend workloads.\nThe referenced MCPGroup must exist in the same namespace.", + "properties": { + "name": { + "description": "Name is the name of the MCPGroup resource in the same namespace", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "imagePullSecrets": { + "description": "ImagePullSecrets allows specifying image pull secrets for the vMCP workload.\nThese are applied to both the vMCP Deployment's PodSpec.ImagePullSecrets\nand to the operator-managed ServiceAccount the vMCP server runs as, so private\nimages are pullable through either path.\n\nMerge semantics with PodTemplateSpec:\nThe deployed PodSpec.ImagePullSecrets is the Kubernetes-native strategic-merge\nunion of this field and spec.podTemplateSpec.spec.imagePullSecrets, merged by\nthe patchStrategy:\"merge\" / patchMergeKey:\"name\" tags on corev1.PodSpec.\n - This field is rendered first as the controller-generated default.\n - spec.podTemplateSpec.spec.imagePullSecrets is then strategic-merge-patched\n on top, keyed by Name. Distinct names from the two sources are unioned in\n the resulting list; entries with the same Name are deduplicated and the\n PodTemplateSpec entry wins on overlap (user override).\n - Order in the resulting list is not guaranteed and should not be relied on:\n strategic merge by name is order-insensitive.\n - The operator-managed ServiceAccount's imagePullSecrets list is populated\n ONLY from this field. spec.podTemplateSpec.spec.imagePullSecrets does not\n reach the ServiceAccount because PodTemplateSpec has no notion of a\n ServiceAccount. To make a secret usable via the ServiceAccount path\n (e.g. for sidecars or init containers that pull images independently),\n list it here rather than under spec.podTemplateSpec.\n\nNote on cross-CRD consistency:\nMCPRegistry currently uses an atomic-replace strategy for its imagePullSecrets\n(the user-provided value replaces the controller-generated list rather than\nbeing merged on top). VirtualMCPServer follows the Kubernetes-native\nstrategic-merge-by-name behavior described above. Aligning the two is tracked\nas a separate follow-up; until then, manifests that set imagePullSecrets on\nboth CRDs will see different override behavior between them.", + "items": { + "description": "LocalObjectReference contains enough information to let you locate the\nreferenced object inside the same namespace.", + "properties": { + "name": { + "default": "", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + } + }, + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "incomingAuth": { + "description": "IncomingAuth configures authentication for clients connecting to the Virtual MCP server.\nMust be explicitly set - use \"anonymous\" type when no authentication is required.\nThis field takes precedence over config.IncomingAuth and should be preferred because it\nsupports Kubernetes-native secret references (SecretKeyRef, ConfigMapRef) for secure\ndynamic discovery of credentials, rather than requiring secrets to be embedded in config.", + "properties": { + "authzConfig": { + "description": "AuthzConfig defines authorization policy configuration\nReuses MCPServer authz patterns", + "properties": { + "configMap": { + "description": "ConfigMap references a ConfigMap containing authorization configuration\nOnly used when Type is \"configMap\"", + "properties": { + "key": { + "default": "authz.json", + "description": "Key is the key in the ConfigMap that contains the authorization configuration", + "type": "string" + }, + "name": { + "description": "Name is the name of the ConfigMap", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "groupClaimName": { + "description": "GroupClaimName is the JWT claim key that contains group membership for the\nprincipal. When set, takes priority over the well-known defaults\n(\"groups\", \"roles\", \"cognito:groups\"). Use this for IDPs that place\ngroups under a URI-style claim (e.g. \"https://example.com/groups\"). When\nType is \"configMap\", a group_claim_name entry in the referenced ConfigMap\nis overridden by this field if both are set.", + "maxLength": 253, + "type": "string" + }, + "groupEntityType": { + "description": "GroupEntityType is the Cedar entity type name used for principal parent\nUIDs synthesised from JWT group/role claims. Defaults to \"THVGroup\" when\nempty. Must match the entity type used in the static entity store for\ntransitive `in` checks (e.g. `ClaimGroup \u2192 PlatformRole`) to resolve.\nNamespaced names (`Foo::Bar`) are not yet supported. When Type is\n\"configMap\", a group_entity_type entry in the referenced ConfigMap is\noverridden by this field if both are set.", + "maxLength": 63, + "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", + "type": "string" + }, + "inline": { + "description": "Inline contains direct authorization configuration\nOnly used when Type is \"inline\"", + "properties": { + "entitiesJson": { + "default": "[]", + "description": "EntitiesJSON is a JSON string representing Cedar entities. Required when\ntransitive policies (e.g. `ClaimGroup \u2192 PlatformRole`) need a static\nentity store; defaults to \"[]\".", + "type": "string" + }, + "policies": { + "description": "Policies is a list of Cedar policy strings", + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "primaryUpstreamProvider": { + "description": "PrimaryUpstreamProvider names the upstream IDP whose access token's\nclaims Cedar should evaluate.\n\nDeprecated: on VirtualMCPServer this field has moved to\nspec.authServerConfig.primaryUpstreamProvider. The old location is\nstill read for one release for backward compatibility; the\nVirtualMCPServer controller emits an AuthzPrimaryUpstreamProviderDeprecated\nWarning event whenever it is consumed, and removal is planned for the\nrelease after the deprecation cycle.\n\nOn MCPServer and MCPRemoteProxy this field has always been a structural\nno-op (those CRDs do not run an embedded auth server). Setting it\ncontinues to surface the AuthzPrimaryUpstreamProviderIgnored advisory\ncondition; the deprecation does not change that behaviour.", + "maxLength": 63, + "minLength": 1, + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "type": "string" + } + }, + "required": [ + "policies" + ], + "type": "object", + "additionalProperties": false + }, + "roleClaimName": { + "description": "RoleClaimName is the JWT claim key that contains role membership for the\nprincipal. When set, the claim is extracted separately from GroupClaimName\nand both are mapped to the configured GroupEntityType. When Type is\n\"configMap\", a role_claim_name entry in the referenced ConfigMap is\noverridden by this field if both are set.", + "maxLength": 253, + "type": "string" + }, + "type": { + "default": "configMap", + "description": "Type is the type of authorization configuration", + "enum": [ + "configMap", + "inline" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "configMap must be set when type is 'configMap', and must not be set otherwise", + "rule": "self.type == 'configMap' ? has(self.configMap) : !has(self.configMap)" + }, + { + "message": "inline must be set when type is 'inline', and must not be set otherwise", + "rule": "self.type == 'inline' ? has(self.inline) : !has(self.inline)" + } + ], + "additionalProperties": false + }, + "oidcConfigRef": { + "description": "OIDCConfigRef references a shared MCPOIDCConfig resource for OIDC authentication.\nThe referenced MCPOIDCConfig must exist in the same namespace as this VirtualMCPServer.\nPer-server overrides (audience, scopes) are specified here; shared provider config\nlives in the MCPOIDCConfig resource.", + "properties": { + "audience": { + "description": "Audience is the expected audience for token validation.\nThis MUST be unique per server to prevent token replay attacks.", + "minLength": 1, + "type": "string" + }, + "name": { + "description": "Name is the name of the MCPOIDCConfig resource", + "minLength": 1, + "type": "string" + }, + "resourceUrl": { + "description": "ResourceURL is the public URL for OAuth protected resource metadata (RFC 9728).\nWhen the server is exposed via Ingress or gateway, set this to the external\nURL that MCP clients connect to. If not specified, defaults to the internal\nKubernetes service URL.", + "type": "string" + }, + "scopes": { + "description": "Scopes is the list of OAuth scopes to advertise in the well-known endpoint (RFC 9728).\nIf empty, defaults to [\"openid\"].", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "required": [ + "audience", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type defines the authentication type: anonymous or oidc\nWhen no authentication is required, explicitly set this to \"anonymous\"", + "enum": [ + "anonymous", + "oidc" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "spec.incomingAuth.oidcConfigRef is required when type is oidc", + "rule": "self.type == 'oidc' ? has(self.oidcConfigRef) : true" + } + ], + "additionalProperties": false + }, + "outgoingAuth": { + "description": "OutgoingAuth configures authentication from Virtual MCP to backend MCPServers.\nThis field takes precedence over config.OutgoingAuth and should be preferred because it\nsupports Kubernetes-native secret references (SecretKeyRef, ConfigMapRef) for secure\ndynamic discovery of credentials, rather than requiring secrets to be embedded in config.", + "properties": { + "backends": { + "additionalProperties": { + "description": "BackendAuthConfig defines authentication configuration for a backend MCPServer", + "properties": { + "externalAuthConfigRef": { + "description": "ExternalAuthConfigRef references an MCPExternalAuthConfig resource\nOnly used when Type is \"externalAuthConfigRef\"", + "properties": { + "name": { + "description": "Name is the name of the MCPExternalAuthConfig resource", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type defines the authentication type", + "enum": [ + "discovered", + "externalAuthConfigRef" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "additionalProperties": false + }, + "description": "Backends defines per-backend authentication overrides\nWorks in all modes (discovered, inline)", + "type": "object" + }, + "default": { + "description": "Default defines default behavior for backends without explicit auth config", + "properties": { + "externalAuthConfigRef": { + "description": "ExternalAuthConfigRef references an MCPExternalAuthConfig resource\nOnly used when Type is \"externalAuthConfigRef\"", + "properties": { + "name": { + "description": "Name is the name of the MCPExternalAuthConfig resource", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type defines the authentication type", + "enum": [ + "discovered", + "externalAuthConfigRef" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "additionalProperties": false + }, + "source": { + "default": "discovered", + "description": "Source defines how backend authentication configurations are determined\n- discovered: Automatically discover from backend's MCPServer.spec.externalAuthConfigRef\n- inline: Explicit per-backend configuration in VirtualMCPServer", + "enum": [ + "discovered", + "inline" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "podTemplateSpec": { + "description": "PodTemplateSpec defines the pod template to use for the Virtual MCP server\nThis allows for customizing the pod configuration beyond what is provided by the other fields.\nNote that to modify the specific container the Virtual MCP server runs in, you must specify\nthe 'vmcp' container name in the PodTemplateSpec.\nThis field accepts a PodTemplateSpec object as JSON/YAML.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "replicas": { + "description": "Replicas is the desired number of vMCP pod replicas.\nVirtualMCPServer creates a single Deployment for the vMCP aggregator process,\nso there is only one replicas field (unlike MCPServer which has separate\nReplicas and BackendReplicas for its two Deployments).\nWhen nil, the operator does not set Deployment.Spec.Replicas, leaving replica\nmanagement to an HPA or other external controller.", + "format": "int32", + "minimum": 0, + "type": "integer" + }, + "serviceAccount": { + "description": "ServiceAccount is the name of an already existing service account to use by the Virtual MCP server.\nIf not specified, a ServiceAccount will be created automatically and used by the Virtual MCP server.", + "type": "string" + }, + "serviceType": { + "default": "ClusterIP", + "description": "ServiceType specifies the Kubernetes service type for the Virtual MCP server", + "enum": [ + "ClusterIP", + "NodePort", + "LoadBalancer" + ], + "type": "string" + }, + "sessionAffinity": { + "default": "ClientIP", + "description": "SessionAffinity controls whether the Service routes repeated client connections to the same pod.\nMCP protocols (SSE, streamable-http) are stateful, so ClientIP is the default.\nSet to \"None\" for stateless servers or when using an external load balancer with its own affinity.", + "enum": [ + "ClientIP", + "None" + ], + "type": "string" + }, + "sessionStorage": { + "description": "SessionStorage configures session storage for stateful horizontal scaling.\nWhen nil, no session storage is configured.", + "properties": { + "address": { + "description": "Address is the Redis server address (required when provider is redis)", + "minLength": 1, + "type": "string" + }, + "db": { + "default": 0, + "description": "DB is the Redis database number", + "format": "int32", + "minimum": 0, + "type": "integer" + }, + "keyPrefix": { + "description": "KeyPrefix is an optional prefix for all Redis keys used by ToolHive", + "type": "string" + }, + "passwordRef": { + "description": "PasswordRef is a reference to a Secret key containing the Redis password", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "provider": { + "description": "Provider is the session storage backend type", + "enum": [ + "memory", + "redis" + ], + "type": "string" + } + }, + "required": [ + "provider" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "address is required", + "rule": "self.provider == 'redis' ? has(self.address) : true" + } + ], + "additionalProperties": false + }, + "telemetryConfigRef": { + "description": "TelemetryConfigRef references an MCPTelemetryConfig resource for shared telemetry configuration.\nThe referenced MCPTelemetryConfig must exist in the same namespace as this VirtualMCPServer.\nCross-namespace references are not supported for security and isolation reasons.", + "properties": { + "name": { + "description": "Name is the name of the MCPTelemetryConfig resource", + "minLength": 1, + "type": "string" + }, + "serviceName": { + "description": "ServiceName overrides the telemetry service name for this specific server.\nThis MUST be unique per server for proper observability (e.g., distinguishing\ntraces and metrics from different servers sharing the same collector).\nIf empty, defaults to the server name with \"thv-\" prefix at runtime.", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "groupRef", + "incomingAuth" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "config.rateLimiting requires sessionStorage with provider 'redis'", + "rule": "!has(self.config) || !has(self.config.rateLimiting) || (has(self.sessionStorage) && self.sessionStorage.provider == 'redis')" + }, + { + "message": "config.rateLimiting.perUser requires incomingAuth.type oidc", + "rule": "!(has(self.config) && has(self.config.rateLimiting) && has(self.config.rateLimiting.perUser)) || (has(self.incomingAuth) && self.incomingAuth.type == 'oidc')" + }, + { + "message": "per-tool perUser rate limiting requires incomingAuth.type oidc", + "rule": "!has(self.config) || !has(self.config.rateLimiting) || !has(self.config.rateLimiting.tools) || self.config.rateLimiting.tools.all(t, !has(t.perUser)) || (has(self.incomingAuth) && self.incomingAuth.type == 'oidc')" + } + ], + "additionalProperties": false + }, + "status": { + "description": "VirtualMCPServerStatus defines the observed state of VirtualMCPServer", + "properties": { + "backendCount": { + "description": "BackendCount is the number of routable backends (ready + unauthenticated).\nExcludes unavailable, degraded, and unknown backends.", + "format": "int32", + "type": "integer" + }, + "conditions": { + "description": "Conditions represent the latest available observations of the VirtualMCPServer's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "discoveredBackends": { + "description": "DiscoveredBackends lists discovered backend configurations from the MCPGroup", + "items": { + "description": "DiscoveredBackend represents a backend server discovered by vMCP runtime.\nThis type is shared with the Kubernetes operator CRD (VirtualMCPServer.Status.DiscoveredBackends).", + "properties": { + "authConfigRef": { + "description": "AuthConfigRef is the name of the discovered MCPExternalAuthConfig (if any)", + "type": "string" + }, + "authType": { + "description": "AuthType is the type of authentication configured", + "type": "string" + }, + "circuitBreakerState": { + "description": "CircuitBreakerState is the current circuit breaker state (closed, open, half-open).\nEmpty when circuit breaker is disabled or not configured.", + "enum": [ + "closed", + "open", + "half-open" + ], + "type": "string" + }, + "circuitLastChanged": { + "description": "CircuitLastChanged is the timestamp when the circuit breaker state last changed.\nEmpty when circuit breaker is disabled or has never changed state.", + "format": "date-time", + "type": "string" + }, + "consecutiveFailures": { + "description": "ConsecutiveFailures is the current count of consecutive health check failures.\nResets to 0 when the backend becomes healthy again.", + "type": "integer" + }, + "lastHealthCheck": { + "description": "LastHealthCheck is the timestamp of the last health check", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "Message provides additional information about the backend status", + "type": "string" + }, + "name": { + "description": "Name is the name of the backend MCPServer", + "type": "string" + }, + "status": { + "description": "Status is the current status of the backend (ready, degraded, unavailable, unauthenticated, unknown).\nUse BackendHealthStatus.ToCRDStatus() to populate this field.", + "type": "string" + }, + "url": { + "description": "URL is the URL of the backend MCPServer", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + }, + "message": { + "description": "Message provides additional information about the current phase", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration is the most recent generation observed for this VirtualMCPServer", + "format": "int64", + "type": "integer" + }, + "oidcConfigHash": { + "description": "OIDCConfigHash is the hash of the referenced MCPOIDCConfig spec for change detection.\nOnly populated when IncomingAuth.OIDCConfigRef is set.", + "type": "string" + }, + "phase": { + "default": "Pending", + "description": "Phase is the current phase of the VirtualMCPServer", + "enum": [ + "Pending", + "Ready", + "Degraded", + "Failed" + ], + "type": "string" + }, + "telemetryConfigHash": { + "description": "TelemetryConfigHash is the hash of the referenced MCPTelemetryConfig spec for change detection.\nOnly populated when TelemetryConfigRef is set.", + "type": "string" + }, + "url": { + "description": "URL is the URL where the Virtual MCP server can be accessed", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/master-standalone/virtualmcpserver-stable-v1beta1.json b/crdSchemas/master-standalone/virtualmcpserver-stable-v1beta1.json new file mode 100644 index 0000000..d8d0a19 --- /dev/null +++ b/crdSchemas/master-standalone/virtualmcpserver-stable-v1beta1.json @@ -0,0 +1,2694 @@ +{ + "description": "VirtualMCPServer is the Schema for the virtualmcpservers API\nVirtualMCPServer aggregates multiple backend MCPServers into a unified endpoint", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "VirtualMCPServerSpec defines the desired state of VirtualMCPServer", + "properties": { + "authServerConfig": { + "description": "AuthServerConfig configures an embedded OAuth authorization server.\nWhen set, the vMCP server acts as an OIDC issuer, drives users through\nupstream IDPs, and issues ToolHive JWTs. The embedded AS becomes the\nIncomingAuth OIDC provider \u2014 its issuer must match IncomingAuth.OIDCConfigRef\nso that tokens it issues are accepted by the vMCP's incoming auth middleware.\nWhen nil, IncomingAuth uses an external IDP and behavior is unchanged.", + "properties": { + "authorizationEndpointBaseUrl": { + "description": "AuthorizationEndpointBaseURL overrides the base URL used for the authorization_endpoint\nin the OAuth discovery document. When set, the discovery document will advertise\n`{authorizationEndpointBaseUrl}/oauth/authorize` instead of `{issuer}/oauth/authorize`.\nAll other endpoints (token, registration, JWKS) remain derived from the issuer.\nThis is useful when the browser-facing authorization endpoint needs to be on a\ndifferent host than the issuer used for backend-to-backend calls.\nMust be a valid HTTPS URL (or HTTP for localhost) without query, fragment, or trailing slash.", + "pattern": "^https?://[^\\s?#]+[^/\\s?#]$", + "type": "string" + }, + "baselineClientScopes": { + "description": "BaselineClientScopes is a baseline set of OAuth 2.0 scopes guaranteed to be\nincluded in every client registration. The embedded auth server unions these\nscopes into the registered set returned by RFC 7591 Dynamic Client\nRegistration, so a client that narrows the `scope` field at /oauth/register\ncan still request the baseline scopes at /oauth/authorize. All values must\nbe present in the upstream-derived scopesSupported set; the auth server\nfails to start if any value is missing.\n\nSecurity: every client registered via /oauth/register will gain the\nability to request these scopes at /oauth/authorize, regardless of what\nthe client itself requested. Keep the baseline narrow (typically\n\"openid\" and \"offline_access\"). Adding a privileged scope here \u2014 e.g.\n\"admin:read\" \u2014 would grant it to every DCR-registered client, including\npublic clients like Claude Code, Cursor, and VS Code.", + "items": { + "minLength": 1, + "pattern": "^[\\x21\\x23-\\x5B\\x5D-\\x7E]+$", + "type": "string" + }, + "maxItems": 10, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "hmacSecretRefs": { + "description": "HMACSecretRefs references Kubernetes Secrets containing symmetric secrets for signing\nauthorization codes and refresh tokens (opaque tokens).\nCurrent secret must be at least 32 bytes and cryptographically random.\nSupports secret rotation via multiple entries (first is current, rest are for verification).\nIf not specified, an ephemeral secret will be auto-generated (development only -\nauth codes and refresh tokens will be invalid after restart).", + "items": { + "description": "SecretKeyRef is a reference to a key within a Secret", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "issuer": { + "description": "Issuer is the issuer identifier for this authorization server.\nThis will be included in the \"iss\" claim of issued tokens.\nMust be a valid HTTPS URL (or HTTP for localhost) without query, fragment, or trailing slash (per RFC 8414).", + "pattern": "^https?://[^\\s?#]+[^/\\s?#]$", + "type": "string" + }, + "primaryUpstreamProvider": { + "description": "PrimaryUpstreamProvider names the upstream IDP whose access token Cedar\nshould read claims from when authorising a request. Must match the name\nof one of the entries in UpstreamProviders. When empty, the controller\nauto-selects the first entry of UpstreamProviders.\n\nOnly meaningful on VirtualMCPServer, where multiple upstream providers\ncan be configured and Cedar needs to pick which token's claims to\nevaluate. The VirtualMCPServer controller validates this field against\nUpstreamProviders at admission and rejects unresolvable values.\n\nOn MCPServer and MCPRemoteProxy this field is structurally present (the\nEmbeddedAuthServerConfig struct is shared) but has no runtime effect:\nthose CRDs are restricted to a single upstream so there is no choice to\nmake. Setting it on those CRDs is silently ignored.", + "maxLength": 63, + "minLength": 1, + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "type": "string" + }, + "signingKeySecretRefs": { + "description": "SigningKeySecretRefs references Kubernetes Secrets containing signing keys for JWT operations.\nSupports key rotation by allowing multiple keys (oldest keys are used for verification only).\nIf not specified, an ephemeral signing key will be auto-generated (development only -\nJWTs will be invalid after restart).", + "items": { + "description": "SecretKeyRef is a reference to a key within a Secret", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "maxItems": 5, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "storage": { + "description": "Storage configures the storage backend for the embedded auth server.\nIf not specified, defaults to in-memory storage.", + "properties": { + "redis": { + "description": "Redis configures the Redis storage backend.\nRequired when type is \"redis\".", + "properties": { + "aclUserConfig": { + "description": "ACLUserConfig configures Redis ACL user authentication.", + "properties": { + "passwordSecretRef": { + "description": "PasswordSecretRef references a Secret containing the Redis ACL password.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "usernameSecretRef": { + "description": "UsernameSecretRef references a Secret containing the Redis ACL username.\nWhen omitted, connections use legacy password-only AUTH. Omit for managed\nRedis tiers that do not support ACL users (e.g. GCP Memorystore Basic/Standard\nHA, Azure Cache for Redis). Set for services that support ACL users (e.g. AWS\nElastiCache non-cluster with Redis 6+ RBAC).", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "passwordSecretRef" + ], + "type": "object", + "additionalProperties": false + }, + "addr": { + "description": "Addr is the Redis server address (host:port). Required for standalone and cluster modes.\nUse for managed Redis services that expose a single endpoint (GCP Memorystore basic tier,\nAWS ElastiCache without cluster mode, or cluster-mode services when clusterMode is true).\nMutually exclusive with sentinelConfig.", + "type": "string" + }, + "clusterMode": { + "description": "ClusterMode enables the Redis Cluster protocol. Set to true when addr points to a\nRedis Cluster discovery endpoint (e.g., GCP Memorystore Cluster, AWS ElastiCache\ncluster mode enabled). Requires addr to be set.", + "type": "boolean" + }, + "dialTimeout": { + "default": "5s", + "description": "DialTimeout is the timeout for establishing connections.\nFormat: Go duration string (e.g., \"5s\", \"1m\").", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "readTimeout": { + "default": "3s", + "description": "ReadTimeout is the timeout for socket reads.\nFormat: Go duration string (e.g., \"3s\", \"1m\").", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "sentinelConfig": { + "description": "SentinelConfig holds Redis Sentinel configuration.\nUse for self-managed Redis with Sentinel-based HA. Mutually exclusive with addr.", + "properties": { + "db": { + "default": 0, + "description": "DB is the Redis database number.", + "format": "int32", + "type": "integer" + }, + "masterName": { + "description": "MasterName is the name of the Redis master monitored by Sentinel.", + "type": "string" + }, + "sentinelAddrs": { + "description": "SentinelAddrs is a list of Sentinel host:port addresses.\nMutually exclusive with SentinelService.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "sentinelService": { + "description": "SentinelService enables automatic discovery from a Kubernetes Service.\nMutually exclusive with SentinelAddrs.", + "properties": { + "name": { + "description": "Name of the Sentinel Service.", + "type": "string" + }, + "namespace": { + "description": "Namespace of the Sentinel Service (defaults to same namespace).", + "type": "string" + }, + "port": { + "default": 26379, + "description": "Port of the Sentinel service.", + "format": "int32", + "type": "integer" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "masterName" + ], + "type": "object", + "additionalProperties": false + }, + "sentinelTls": { + "description": "SentinelTLS configures TLS for connections to Sentinel instances.\nOnly applies when sentinelConfig is set. Presence of this field enables TLS.", + "properties": { + "caCertSecretRef": { + "description": "CACertSecretRef references a Secret containing a PEM-encoded CA certificate\nfor verifying the server. When not specified, system root CAs are used.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "insecureSkipVerify": { + "description": "InsecureSkipVerify skips TLS certificate verification.\nUse when connecting to services with self-signed certificates.", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "tls": { + "description": "TLS configures TLS for connections to the Redis/Valkey master.\nPresence of this field enables TLS. Omit to use plaintext.", + "properties": { + "caCertSecretRef": { + "description": "CACertSecretRef references a Secret containing a PEM-encoded CA certificate\nfor verifying the server. When not specified, system root CAs are used.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "insecureSkipVerify": { + "description": "InsecureSkipVerify skips TLS certificate verification.\nUse when connecting to services with self-signed certificates.", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "writeTimeout": { + "default": "3s", + "description": "WriteTimeout is the timeout for socket writes.\nFormat: Go duration string (e.g., \"3s\", \"1m\").", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + } + }, + "required": [ + "aclUserConfig" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "exactly one of addr or sentinelConfig must be set", + "rule": "(has(self.addr) && self.addr.size() > 0) != has(self.sentinelConfig)" + }, + { + "message": "clusterMode requires addr to be set", + "rule": "!(has(self.clusterMode) && self.clusterMode) || (has(self.addr) && self.addr.size() > 0)" + } + ], + "additionalProperties": false + }, + "type": { + "default": "memory", + "description": "Type specifies the storage backend type.\nValid values: \"memory\" (default), \"redis\".", + "enum": [ + "memory", + "redis" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "tokenLifespans": { + "description": "TokenLifespans configures the duration that various tokens are valid.\nIf not specified, defaults are applied (access: 1h, refresh: 7d, authCode: 10m).", + "properties": { + "accessTokenLifespan": { + "description": "AccessTokenLifespan is the duration that access tokens are valid.\nFormat: Go duration string (e.g., \"1h\", \"30m\", \"24h\").\nIf empty, defaults to 1 hour.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "authCodeLifespan": { + "description": "AuthCodeLifespan is the duration that authorization codes are valid.\nFormat: Go duration string (e.g., \"10m\", \"5m\").\nIf empty, defaults to 10 minutes.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "refreshTokenLifespan": { + "description": "RefreshTokenLifespan is the duration that refresh tokens are valid.\nFormat: Go duration string (e.g., \"168h\", \"7d\" as \"168h\").\nIf empty, defaults to 7 days (168h).", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "upstreamProviders": { + "description": "UpstreamProviders configures connections to upstream Identity Providers.\nThe embedded auth server delegates authentication to these providers.\nMCPServer and MCPRemoteProxy support a single upstream; VirtualMCPServer supports multiple.", + "items": { + "description": "UpstreamProviderConfig defines configuration for an upstream Identity Provider.\n\nExactly one of OIDCConfig or OAuth2Config must be set and must match the\ndeclared Type: oidc-typed providers set OIDCConfig, oauth2-typed providers\nset OAuth2Config. The CEL rule below enforces the pairing at admission; the\nmatching Go-level check in validateUpstreamProvider provides defense-in-depth\nfor stored objects.\n\nThe rule is structured as a chain of equality checks ending in an explicit\n`false`, so adding a new UpstreamProviderType value without extending this\nrule fails admission instead of silently demanding the OAuth2 shape. When\nadding a new type, extend both this rule and validateUpstreamProvider.", + "properties": { + "name": { + "description": "Name uniquely identifies this upstream provider.\nUsed for routing decisions and session binding in multi-upstream scenarios.\nMust be lowercase alphanumeric with hyphens (DNS-label-like).", + "maxLength": 63, + "minLength": 1, + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "type": "string" + }, + "oauth2Config": { + "description": "OAuth2Config contains OAuth 2.0-specific configuration.\nRequired when Type is \"oauth2\", must be nil when Type is \"oidc\".", + "properties": { + "additionalAuthorizationParams": { + "additionalProperties": { + "type": "string" + }, + "description": "AdditionalAuthorizationParams are extra query parameters to include in\nauthorization requests sent to the upstream provider.\nThis is useful for providers that require custom parameters, such as\nGoogle's access_type=offline for obtaining refresh tokens.\nFramework-managed parameters (response_type, client_id, redirect_uri,\nscope, state, code_challenge, code_challenge_method, nonce) are not allowed.", + "maxProperties": 16, + "type": "object" + }, + "authorizationEndpoint": { + "description": "AuthorizationEndpoint is the URL for the OAuth authorization endpoint.", + "pattern": "^https?://.*$", + "type": "string" + }, + "clientId": { + "description": "ClientID is the OAuth 2.0 client identifier registered with the upstream IDP.\nMutually exclusive with DCRConfig: when DCRConfig is set, ClientID is obtained\nat runtime via RFC 7591 Dynamic Client Registration and must be left empty.", + "type": "string" + }, + "clientSecretRef": { + "description": "ClientSecretRef references a Kubernetes Secret containing the OAuth 2.0 client secret.\nOptional for public clients using PKCE instead of client secret.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "dcrConfig": { + "description": "DCRConfig enables RFC 7591 Dynamic Client Registration against the upstream\nauthorization server. When set, the client credentials are obtained at\nruntime rather than being pre-provisioned, and ClientID must be left empty.\nMutually exclusive with ClientID.", + "properties": { + "discoveryUrl": { + "description": "DiscoveryURL is the RFC 8414 / OIDC Discovery document URL. The resolver\nissues a single GET against this URL (no well-known-path fallback) and\nreads registration_endpoint, authorization_endpoint, token_endpoint,\ntoken_endpoint_auth_methods_supported, and scopes_supported from the\nresponse.\nMutually exclusive with RegistrationEndpoint.\nHTTPS is required because the registration endpoint resolved from this\ndocument carries the initial access token and the issued client_secret\n(RFC 7591 \u00a73, RFC 8414 \u00a73). MaxLength is a defensive size cap (etcd\nobject budget, regex evaluation cost) and matches the conventional URL\nlength cap.", + "maxLength": 2048, + "pattern": "^https://[^\\s?#]+[^/\\s?#]$", + "type": "string" + }, + "initialAccessTokenRef": { + "description": "InitialAccessTokenRef is an optional reference to a Kubernetes Secret\ncarrying an RFC 7591 \u00a73 initial access token. When set, the resolver\npresents the token value as a Bearer credential on the registration\nrequest. Mirrors the ClientSecretRef pattern.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "registrationEndpoint": { + "description": "RegistrationEndpoint is the RFC 7591 registration endpoint URL used\ndirectly, bypassing discovery. When using this field, the caller is\nexpected to also supply AuthorizationEndpoint, TokenEndpoint, and an\nexplicit Scopes list on the parent OAuth2UpstreamConfig.\nMutually exclusive with DiscoveryURL.\nHTTPS is required because the registration endpoint carries the initial\naccess token and the issued client_secret (RFC 7591 \u00a73, RFC 8414 \u00a73).\nMaxLength is a defensive size cap (etcd object budget, regex evaluation\ncost) and matches the conventional URL length cap.", + "maxLength": 2048, + "pattern": "^https://[^\\s?#]+[^/\\s?#]$", + "type": "string" + }, + "softwareId": { + "description": "SoftwareID is the RFC 7591 \"software_id\" registration metadata value,\nidentifying the client software independent of any particular\nregistration instance. Typically a UUID or short identifier.", + "maxLength": 255, + "type": "string" + }, + "softwareStatement": { + "description": "SoftwareStatement is the RFC 7591 \"software_statement\" JWT asserting\nmetadata about the client software, signed by a party the authorization\nserver trusts.\n\nStored inline on the CR. The JWT is signed but not encrypted, so its\ncontents are visible to anyone with get/list/watch on this resource and\nappear in etcd backups in plaintext. Treat the value as non-confidential\n(signed attestation, not a secret). Operators that rotate software\nstatements like bearer credentials should keep them at the authorization\nserver side and rely on the registration endpoint's initial access\ntoken (see InitialAccessTokenRef) instead of placing them on the CR.\n\nBounded to 16384 characters as a defensive size cap (etcd object\nbudget, regex evaluation cost). Real-world signed statements with\nembedded x5c certificate chains, JWKS keys, or OIDC-Federation\ntrust-framework metadata routinely exceed 4 KB.", + "maxLength": 16384, + "type": "string" + } + }, + "type": "object", + "x-kubernetes-validations": [ + { + "message": "exactly one of discoveryUrl or registrationEndpoint must be set", + "rule": "has(self.discoveryUrl) != has(self.registrationEndpoint)" + } + ], + "additionalProperties": false + }, + "identityFromToken": { + "description": "IdentityFromToken extracts user identity (subject, name, email) directly\nfrom the OAuth2 token-endpoint response body using gjson dot-notation paths.\nWhen set, the embedded auth server skips the userinfo HTTP call entirely\nand resolves identity from the token response. See IdentityFromTokenConfig\nfor trust-model and uniqueness considerations.", + "properties": { + "emailPath": { + "description": "EmailPath is the dot-notation path to the email address field in the token response.\nIf not specified or if the path does not resolve to a string, the email is omitted.\nOmit the field entirely rather than setting it to an empty string.", + "maxLength": 256, + "minLength": 1, + "type": "string" + }, + "namePath": { + "description": "NamePath is the dot-notation path to the display name field in the token response.\nIf not specified or if the path does not resolve to a string, the display name is omitted.\nOmit the field entirely rather than setting it to an empty string.", + "maxLength": 256, + "minLength": 1, + "type": "string" + }, + "subjectPath": { + "description": "SubjectPath is the dot-notation path to the subject (user ID) field in the token response.\nWarning: claims read from the token response are trusted only via TLS, not\ncryptographically verified; prefer OIDC ID tokens when verifiable claims are required.\nExample: \"authed_user.id\" for Slack (top-level token-response field). For providers\nwhose token response embeds the access token as a JWT (e.g. Snowflake), use the\n\"@upstreamjwt\" modifier to decode the payload, e.g. \"access_token|@upstreamjwt|sub\".\nThe \"@upstreamjwt\" modifier performs no signature verification either.", + "maxLength": 256, + "minLength": 1, + "type": "string" + } + }, + "required": [ + "subjectPath" + ], + "type": "object", + "additionalProperties": false + }, + "redirectUri": { + "description": "RedirectURI is the callback URL where the upstream IDP will redirect after authentication.\nWhen not specified, defaults to `{resourceUrl}/oauth/callback` where `resourceUrl` is the\nURL associated with the resource (e.g., MCPServer or vMCP) using this config.", + "type": "string" + }, + "scopes": { + "description": "Scopes are the OAuth scopes to request from the upstream IDP.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "tokenEndpoint": { + "description": "TokenEndpoint is the URL for the OAuth token endpoint.", + "pattern": "^https?://.*$", + "type": "string" + }, + "tokenResponseMapping": { + "description": "TokenResponseMapping configures custom field extraction from non-standard token responses.\nSome OAuth providers (e.g., GovSlack) nest token fields under non-standard paths\ninstead of returning them at the top level. When set, ToolHive performs the token\nexchange HTTP call directly and extracts fields using the configured dot-notation paths.\nIf nil, standard OAuth 2.0 token response parsing is used.\nFor extracting user identity from the token response, see IdentityFromToken.", + "properties": { + "accessTokenPath": { + "description": "AccessTokenPath is the dot-notation path to the access token in the response.\nExample: \"authed_user.access_token\"", + "minLength": 1, + "type": "string" + }, + "expiresInPath": { + "description": "ExpiresInPath is the dot-notation path to the expires_in value (in seconds).\nIf not specified, defaults to \"expires_in\".", + "type": "string" + }, + "refreshTokenPath": { + "description": "RefreshTokenPath is the dot-notation path to the refresh token in the response.\nIf not specified, defaults to \"refresh_token\".", + "type": "string" + }, + "scopePath": { + "description": "ScopePath is the dot-notation path to the scope string in the response.\nIf not specified, defaults to \"scope\".", + "type": "string" + } + }, + "required": [ + "accessTokenPath" + ], + "type": "object", + "additionalProperties": false + }, + "userInfo": { + "description": "UserInfo contains configuration for fetching user information from the upstream provider.\nWhen omitted and IdentityFromToken is also unset, the embedded auth server runs in\nsynthesis mode for this upstream: a non-PII subject derived from the access token, no\nName/Email. Use this shape for upstreams with no userinfo surface and no identity in\nthe token response (e.g., MCP authorization servers per the MCP spec). When\nIdentityFromToken is set instead, identity is resolved from the token response body\n(e.g., Snowflake's \"username\" field, Slack's \"authed_user.id\"); the userinfo HTTP call\nis skipped entirely.", + "properties": { + "additionalHeaders": { + "additionalProperties": { + "type": "string" + }, + "description": "AdditionalHeaders contains extra headers to include in the userinfo request.\nUseful for providers that require specific headers (e.g., GitHub's Accept header).", + "type": "object" + }, + "endpointUrl": { + "description": "EndpointURL is the URL of the userinfo endpoint.", + "pattern": "^https?://.*$", + "type": "string" + }, + "fieldMapping": { + "description": "FieldMapping contains custom field mapping configuration for non-standard providers.\nIf nil, standard OIDC field names are used (\"sub\", \"name\", \"email\").", + "properties": { + "emailFields": { + "description": "EmailFields is an ordered list of field names to try for the email address.\nThe first non-empty value found will be used.\nDefault: [\"email\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "nameFields": { + "description": "NameFields is an ordered list of field names to try for the display name.\nThe first non-empty value found will be used.\nDefault: [\"name\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "subjectFields": { + "description": "SubjectFields is an ordered list of field names to try for the user ID.\nThe first non-empty value found will be used.\nDefault: [\"sub\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "type": "object", + "additionalProperties": false + }, + "httpMethod": { + "description": "HTTPMethod is the HTTP method to use for the userinfo request.\nIf not specified, defaults to GET.", + "enum": [ + "GET", + "POST" + ], + "type": "string" + } + }, + "required": [ + "endpointUrl" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "authorizationEndpoint", + "tokenEndpoint" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "exactly one of clientId or dcrConfig must be set", + "rule": "(has(self.clientId) && size(self.clientId) > 0) ? !has(self.dcrConfig) : has(self.dcrConfig)" + }, + { + "message": "clientSecretRef must not be set when dcrConfig is set; the client_secret is obtained at runtime via Dynamic Client Registration", + "rule": "!(has(self.dcrConfig) && has(self.clientSecretRef))" + } + ], + "additionalProperties": false + }, + "oidcConfig": { + "description": "OIDCConfig contains OIDC-specific configuration.\nRequired when Type is \"oidc\", must be nil when Type is \"oauth2\".", + "properties": { + "additionalAuthorizationParams": { + "additionalProperties": { + "type": "string" + }, + "description": "AdditionalAuthorizationParams are extra query parameters to include in\nauthorization requests sent to the upstream provider.\nThis is useful for providers that require custom parameters, such as\nGoogle's access_type=offline for obtaining refresh tokens.\nNote: when using access_type=offline, also set explicit scopes to avoid\nthe default offline_access scope being sent alongside it.\nFramework-managed parameters (response_type, client_id, redirect_uri,\nscope, state, code_challenge, code_challenge_method, nonce) are not allowed.", + "maxProperties": 16, + "type": "object" + }, + "clientId": { + "description": "ClientID is the OAuth 2.0 client identifier registered with the upstream IDP.", + "type": "string" + }, + "clientSecretRef": { + "description": "ClientSecretRef references a Kubernetes Secret containing the OAuth 2.0 client secret.\nOptional for public clients using PKCE instead of client secret.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "issuerUrl": { + "description": "IssuerURL is the OIDC issuer URL for automatic endpoint discovery.\nMust be a valid HTTPS URL.", + "pattern": "^https://.*$", + "type": "string" + }, + "redirectUri": { + "description": "RedirectURI is the callback URL where the upstream IDP will redirect after authentication.\nWhen not specified, defaults to `{resourceUrl}/oauth/callback` where `resourceUrl` is the\nURL associated with the resource (e.g., MCPServer or vMCP) using this config.", + "type": "string" + }, + "scopes": { + "description": "Scopes are the OAuth scopes to request from the upstream IDP.\nIf not specified, defaults to [\"openid\", \"offline_access\"].\nWhen using additionalAuthorizationParams with provider-specific refresh token\nmechanisms (e.g., Google's access_type=offline), set explicit scopes to avoid\nsending both offline_access and the provider-specific parameter.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "userInfoOverride": { + "description": "UserInfoOverride allows customizing UserInfo fetching behavior for OIDC providers.\nBy default, the UserInfo endpoint is discovered automatically via OIDC discovery.\nUse this to override the endpoint URL, HTTP method, or field mappings for providers\nthat return non-standard claim names in their UserInfo response.", + "properties": { + "additionalHeaders": { + "additionalProperties": { + "type": "string" + }, + "description": "AdditionalHeaders contains extra headers to include in the userinfo request.\nUseful for providers that require specific headers (e.g., GitHub's Accept header).", + "type": "object" + }, + "endpointUrl": { + "description": "EndpointURL is the URL of the userinfo endpoint.", + "pattern": "^https?://.*$", + "type": "string" + }, + "fieldMapping": { + "description": "FieldMapping contains custom field mapping configuration for non-standard providers.\nIf nil, standard OIDC field names are used (\"sub\", \"name\", \"email\").", + "properties": { + "emailFields": { + "description": "EmailFields is an ordered list of field names to try for the email address.\nThe first non-empty value found will be used.\nDefault: [\"email\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "nameFields": { + "description": "NameFields is an ordered list of field names to try for the display name.\nThe first non-empty value found will be used.\nDefault: [\"name\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "subjectFields": { + "description": "SubjectFields is an ordered list of field names to try for the user ID.\nThe first non-empty value found will be used.\nDefault: [\"sub\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "type": "object", + "additionalProperties": false + }, + "httpMethod": { + "description": "HTTPMethod is the HTTP method to use for the userinfo request.\nIf not specified, defaults to GET.", + "enum": [ + "GET", + "POST" + ], + "type": "string" + } + }, + "required": [ + "endpointUrl" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "clientId", + "issuerUrl" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type specifies the provider type: \"oidc\" or \"oauth2\"", + "enum": [ + "oidc", + "oauth2" + ], + "type": "string" + } + }, + "required": [ + "name", + "type" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "type must be 'oidc' or 'oauth2'; oidcConfig must be set when type is 'oidc' and oauth2Config must be set when type is 'oauth2' (and the other must not be set)", + "rule": "self.type == 'oidc' ? (has(self.oidcConfig) && !has(self.oauth2Config)) : self.type == 'oauth2' ? (has(self.oauth2Config) && !has(self.oidcConfig)) : false" + } + ], + "additionalProperties": false + }, + "minItems": 1, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "required": [ + "issuer", + "upstreamProviders" + ], + "type": "object", + "additionalProperties": false + }, + "config": { + "description": "Config is the Virtual MCP server configuration.\nThe audit config from here is also supported, but not required.", + "properties": { + "aggregation": { + "description": "Aggregation defines tool aggregation and conflict resolution strategies.\nSupports ToolConfigRef for Kubernetes-native MCPToolConfig resource references.", + "properties": { + "conflictResolution": { + "default": "prefix", + "description": "ConflictResolution defines the strategy for resolving tool name conflicts.\n- prefix: Automatically prefix tool names with workload identifier\n- priority: First workload in priority order wins\n- manual: Explicitly define overrides for all conflicts", + "enum": [ + "prefix", + "priority", + "manual" + ], + "type": "string" + }, + "conflictResolutionConfig": { + "description": "ConflictResolutionConfig provides configuration for the chosen strategy.", + "properties": { + "prefixFormat": { + "default": "{workload}_", + "description": "PrefixFormat defines the prefix format for the \"prefix\" strategy.\nSupports placeholders: {workload}, {workload}_, {workload}.", + "type": "string" + }, + "priorityOrder": { + "description": "PriorityOrder defines the workload priority order for the \"priority\" strategy.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "additionalProperties": false + }, + "excludeAllTools": { + "description": "ExcludeAllTools hides all backend tools from MCP clients when true.\nHidden tools are NOT advertised in tools/list responses, but they ARE\navailable in the routing table for composite tools to use.\nThis enables the use case where you want to hide raw backend tools from\ndirect client access while exposing curated composite tool workflows.", + "type": "boolean" + }, + "tools": { + "description": "Tools defines per-workload tool filtering and overrides.", + "items": { + "description": "WorkloadToolConfig defines tool filtering and overrides for a specific workload.", + "properties": { + "excludeAll": { + "description": "ExcludeAll hides all tools from this workload from MCP clients when true.\nHidden tools are NOT advertised in tools/list responses, but they ARE\navailable in the routing table for composite tools to use.\nThis enables the use case where you want to hide raw backend tools from\ndirect client access while exposing curated composite tool workflows.", + "type": "boolean" + }, + "filter": { + "description": "Filter is an allow-list of tool names to advertise to MCP clients.\nTools NOT in this list are hidden from clients (not in tools/list response)\nbut remain available in the routing table for composite tools to use.\nThis enables selective exposure of backend tools while allowing composite\nworkflows to orchestrate all backend capabilities.\nOnly used if ToolConfigRef is not specified.", + "items": { + "type": "string" + }, + "type": "array" + }, + "overrides": { + "additionalProperties": { + "description": "ToolOverride defines tool name, description, and annotation overrides.", + "properties": { + "annotations": { + "description": "Annotations overrides specific tool annotation fields.\nOnly specified fields are overridden; others pass through from the backend.", + "properties": { + "destructiveHint": { + "description": "DestructiveHint overrides the destructive hint annotation.", + "type": "boolean" + }, + "idempotentHint": { + "description": "IdempotentHint overrides the idempotent hint annotation.", + "type": "boolean" + }, + "openWorldHint": { + "description": "OpenWorldHint overrides the open-world hint annotation.", + "type": "boolean" + }, + "readOnlyHint": { + "description": "ReadOnlyHint overrides the read-only hint annotation.", + "type": "boolean" + }, + "title": { + "description": "Title overrides the human-readable title annotation.", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "description": { + "description": "Description is the new tool description.", + "type": "string" + }, + "name": { + "description": "Name is the new tool name (for renaming).", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "description": "Overrides is an inline map of tool overrides for renaming and description changes.\nOverrides are applied to tools before conflict resolution and affect both\nadvertising and routing (the overridden name is used everywhere).\nOnly used if ToolConfigRef is not specified.", + "type": "object" + }, + "toolConfigRef": { + "description": "ToolConfigRef references an MCPToolConfig resource for tool filtering and renaming.\nIf specified, Filter and Overrides are ignored.\nOnly used when running in Kubernetes with the operator.", + "properties": { + "name": { + "description": "Name is the name of the MCPToolConfig resource in the same namespace.", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "workload": { + "description": "Workload is the name of the backend MCPServer workload.", + "type": "string" + } + }, + "required": [ + "workload" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + } + }, + "type": "object", + "additionalProperties": false + }, + "audit": { + "description": "Audit configures audit logging for the Virtual MCP server.\nWhen present, audit logs include MCP protocol operations.\nSee audit.Config for available configuration options.", + "properties": { + "component": { + "description": "Component is the component name to use in audit events.", + "type": "string" + }, + "detectApplicationErrors": { + "default": true, + "description": "DetectApplicationErrors controls whether the audit middleware inspects\nJSON-RPC response bodies for application-level errors when the HTTP\nstatus code indicates success (2xx). When enabled, a small prefix of\nthe response body is buffered to detect JSON-RPC error fields,\nindependent of the IncludeResponseData setting.", + "type": "boolean" + }, + "enabled": { + "default": false, + "description": "Enabled controls whether audit logging is enabled.\nWhen true, enables audit logging with the configured options.", + "type": "boolean" + }, + "eventTypes": { + "description": "EventTypes specifies which event types to audit. If empty, all events are audited.", + "items": { + "type": "string" + }, + "type": "array" + }, + "excludeEventTypes": { + "description": "ExcludeEventTypes specifies which event types to exclude from auditing.\nThis takes precedence over EventTypes.", + "items": { + "type": "string" + }, + "type": "array" + }, + "includeRequestData": { + "default": false, + "description": "IncludeRequestData determines whether to include request data in audit logs.", + "type": "boolean" + }, + "includeResponseData": { + "default": false, + "description": "IncludeResponseData determines whether to include response data in audit logs.", + "type": "boolean" + }, + "logFile": { + "description": "LogFile specifies the file path for audit logs. If empty, logs to stdout.", + "type": "string" + }, + "maxDataSize": { + "default": 1024, + "description": "MaxDataSize limits the size of request/response data included in audit logs (in bytes).", + "type": "integer" + } + }, + "type": "object", + "additionalProperties": false + }, + "backends": { + "description": "Backends defines pre-configured backend servers for static mode.\nWhen OutgoingAuth.Source is \"inline\", this field contains the full list of backend\nservers with their URLs and transport types, eliminating the need for K8s API access.\nWhen OutgoingAuth.Source is \"discovered\", this field is empty and backends are\ndiscovered at runtime via Kubernetes API.", + "items": { + "description": "StaticBackendConfig defines a pre-configured backend server for static mode.\nThis allows vMCP to operate without Kubernetes API access by embedding all backend\ninformation directly in the configuration.", + "properties": { + "caBundlePath": { + "description": "CABundlePath is the file path to a custom CA certificate bundle for TLS verification.\nOnly valid when Type is \"entry\". The operator mounts CA bundles at\n/etc/toolhive/ca-bundles//ca.crt.", + "type": "string" + }, + "metadata": { + "additionalProperties": { + "type": "string" + }, + "description": "Metadata is a custom key-value map for storing additional backend information\nsuch as labels, tags, or other arbitrary data (e.g., \"env\": \"prod\", \"region\": \"us-east-1\").\nThis is NOT Kubernetes ObjectMeta - it's a simple string map for user-defined metadata.\nReserved keys: \"group\" is automatically set by vMCP and any user-provided value will be overridden.", + "type": "object" + }, + "name": { + "description": "Name is the backend identifier.\nMust match the backend name from the MCPGroup for auth config resolution.", + "type": "string" + }, + "transport": { + "description": "Transport is the MCP transport protocol: \"sse\" or \"streamable-http\"\nOnly network transports supported by vMCP client are allowed.", + "enum": [ + "sse", + "streamable-http" + ], + "type": "string" + }, + "type": { + "description": "Type is the backend workload type: \"entry\" for MCPServerEntry backends, or empty\nfor container/proxy backends. Entry backends connect directly to remote MCP servers.", + "enum": [ + "entry", + "" + ], + "type": "string" + }, + "url": { + "description": "URL is the backend's MCP server base URL.", + "pattern": "^https?://", + "type": "string" + } + }, + "required": [ + "name", + "transport", + "url" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + }, + "compositeToolRefs": { + "description": "CompositeToolRefs references VirtualMCPCompositeToolDefinition resources\nfor complex, reusable workflows. Only applicable when running in Kubernetes.\nReferenced resources must be in the same namespace as the VirtualMCPServer.", + "items": { + "description": "CompositeToolRef defines a reference to a VirtualMCPCompositeToolDefinition resource.\nThe referenced resource must be in the same namespace as the VirtualMCPServer.", + "properties": { + "name": { + "description": "Name is the name of the VirtualMCPCompositeToolDefinition resource in the same namespace.", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + }, + "compositeTools": { + "description": "CompositeTools defines inline composite tool workflows.\nFull workflow definitions are embedded in the configuration.\nFor Kubernetes, complex workflows can also reference VirtualMCPCompositeToolDefinition CRDs.", + "items": { + "description": "CompositeToolConfig defines a composite tool workflow.\nThis matches the YAML structure from the proposal (lines 173-255).", + "properties": { + "description": { + "description": "Description describes what the workflow does.", + "type": "string" + }, + "name": { + "description": "Name is the workflow name (unique identifier).", + "type": "string" + }, + "output": { + "description": "Output defines the structured output schema for this workflow.\nIf not specified, the workflow returns the last step's output (backward compatible).", + "properties": { + "properties": { + "additionalProperties": { + "description": "OutputProperty defines a single output property.\nFor non-object types, Value is required.\nFor object types, either Value or Properties must be specified (but not both).", + "properties": { + "default": { + "description": "Default is the fallback value if template expansion fails.\nType coercion is applied to match the declared Type.", + "x-kubernetes-preserve-unknown-fields": true + }, + "description": { + "description": "Description is a human-readable description exposed to clients and models", + "type": "string" + }, + "properties": { + "description": "Properties defines nested properties for object types.\nEach nested property has full metadata (type, description, value/properties).", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "type": { + "description": "Type is the JSON Schema type: \"string\", \"integer\", \"number\", \"boolean\", \"object\", \"array\"", + "enum": [ + "string", + "integer", + "number", + "boolean", + "object", + "array" + ], + "type": "string" + }, + "value": { + "description": "Value is a template string for constructing the runtime value.\nFor object types, this can be a JSON string that will be deserialized.\nSupports template syntax: {{.steps.step_id.output.field}}, {{.params.param_name}}", + "type": "string" + }, + "additionalProperties": false + }, + "required": [ + "type" + ], + "type": "object", + "additionalProperties": false + }, + "description": "Properties defines the output properties.\nMap key is the property name, value is the property definition.", + "type": "object" + }, + "required": { + "description": "Required lists property names that must be present in the output.", + "items": { + "type": "string" + }, + "type": "array" + }, + "additionalProperties": false + }, + "required": [ + "properties" + ], + "type": "object", + "additionalProperties": false + }, + "parameters": { + "description": "Parameters defines input parameter schema in JSON Schema format.\nShould be a JSON Schema object with \"type\": \"object\" and \"properties\".\nExample:\n {\n \"type\": \"object\",\n \"properties\": {\n \"param1\": {\"type\": \"string\", \"default\": \"value\"},\n \"param2\": {\"type\": \"integer\"}\n },\n \"required\": [\"param2\"]\n }\n\nWe use json.Map rather than a typed struct because JSON Schema is highly\nflexible with many optional fields (default, enum, minimum, maximum, pattern,\nitems, additionalProperties, oneOf, anyOf, allOf, etc.). Using json.Map\nallows full JSON Schema compatibility without needing to define every possible\nfield, and matches how the MCP SDK handles inputSchema.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "steps": { + "description": "Steps are the workflow steps to execute.", + "items": { + "description": "WorkflowStepConfig defines a single workflow step.\nThis matches the proposal's step configuration (lines 180-255).", + "properties": { + "arguments": { + "description": "Arguments is a map of argument values with template expansion support.\nSupports Go template syntax with .params and .steps for string values.\nNon-string values (integers, booleans, arrays, objects) are passed as-is.\nNote: the templating is only supported on the first level of the key-value pairs.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "collection": { + "description": "Collection is a Go template expression that resolves to a JSON array or a slice.\nOnly used when Type is \"forEach\".", + "type": "string" + }, + "condition": { + "description": "Condition is a template expression that determines if the step should execute", + "type": "string" + }, + "defaultResults": { + "description": "DefaultResults provides fallback output values when this step is skipped\n(due to condition evaluating to false) or fails (when onError.action is \"continue\").\nEach key corresponds to an output field name referenced by downstream steps.\nRequired if the step may be skipped AND downstream steps reference this step's output.", + "x-kubernetes-preserve-unknown-fields": true + }, + "dependsOn": { + "description": "DependsOn lists step IDs that must complete before this step", + "items": { + "type": "string" + }, + "type": "array" + }, + "id": { + "description": "ID is the unique identifier for this step.", + "type": "string" + }, + "itemVar": { + "description": "ItemVar is the variable name used to reference the current item in forEach templates.\nDefaults to \"item\" if not specified.\nOnly used when Type is \"forEach\".", + "type": "string" + }, + "maxIterations": { + "description": "MaxIterations limits the number of items that can be iterated over.\nDefaults to 100, hard cap at 1000.\nOnly used when Type is \"forEach\".", + "type": "integer" + }, + "maxParallel": { + "description": "MaxParallel limits the number of concurrent iterations in a forEach step.\nDefaults to the DAG executor's maxParallel (10).\nOnly used when Type is \"forEach\".", + "type": "integer" + }, + "message": { + "description": "Message is the elicitation message\nOnly used when Type is \"elicitation\"", + "type": "string" + }, + "onCancel": { + "description": "OnCancel defines the action to take when the user cancels/dismisses the elicitation\nOnly used when Type is \"elicitation\"", + "properties": { + "action": { + "default": "abort", + "description": "Action defines the action to take when the user declines or cancels\n- skip_remaining: Skip remaining steps in the workflow\n- abort: Abort the entire workflow execution\n- continue: Continue to the next step", + "enum": [ + "skip_remaining", + "abort", + "continue" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "onDecline": { + "description": "OnDecline defines the action to take when the user explicitly declines the elicitation\nOnly used when Type is \"elicitation\"", + "properties": { + "action": { + "default": "abort", + "description": "Action defines the action to take when the user declines or cancels\n- skip_remaining: Skip remaining steps in the workflow\n- abort: Abort the entire workflow execution\n- continue: Continue to the next step", + "enum": [ + "skip_remaining", + "abort", + "continue" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "onError": { + "description": "OnError defines error handling behavior", + "properties": { + "action": { + "default": "abort", + "description": "Action defines the action to take on error", + "enum": [ + "abort", + "continue", + "retry" + ], + "type": "string" + }, + "retryCount": { + "description": "RetryCount is the maximum number of retries\nOnly used when Action is \"retry\"", + "type": "integer" + }, + "retryDelay": { + "description": "RetryDelay is the delay between retry attempts\nOnly used when Action is \"retry\"", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "schema": { + "description": "Schema defines the expected response schema for elicitation", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "step": { + "description": "InnerStep defines the step to execute for each item in the collection.\nOnly used when Type is \"forEach\". Only tool-type inner steps are supported.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "timeout": { + "description": "Timeout is the maximum execution time for this step", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "tool": { + "description": "Tool is the tool to call (format: \"workload.tool_name\")\nOnly used when Type is \"tool\"", + "type": "string" + }, + "type": { + "default": "tool", + "description": "Type is the step type (tool, elicitation, etc.)", + "enum": [ + "tool", + "elicitation", + "forEach" + ], + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + }, + "timeout": { + "description": "Timeout is the maximum workflow execution time.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + } + }, + "required": [ + "name", + "steps" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + }, + "groupRef": { + "description": "Group references an existing MCPGroup that defines backend workloads.\nIn standalone CLI mode, this is set from the YAML config file.\nIn Kubernetes, the operator populates this from spec.groupRef during conversion.", + "type": "string" + }, + "incomingAuth": { + "description": "IncomingAuth configures how clients authenticate to the virtual MCP server.\nWhen using the Kubernetes operator, this is populated by the converter from\nVirtualMCPServerSpec.IncomingAuth and any values set here will be superseded.", + "properties": { + "authz": { + "description": "Authz contains authorization configuration (optional).", + "properties": { + "entitiesJson": { + "description": "EntitiesJSON is a JSON string representing Cedar entities. Required for\nenterprise policies that rely on transitive relationships (e.g.\n`ClaimGroup \u2192 PlatformRole`) \u2014 without it the Cedar authorizer is\nconstructed with an empty entity store and `in` checks against absent\nentities silently evaluate to false. Defaults to \"[]\" when empty.", + "type": "string" + }, + "groupClaimName": { + "description": "GroupClaimName is the JWT claim key that contains group membership for\nthe principal. When set, takes priority over the well-known defaults\n(\"groups\", \"roles\", \"cognito:groups\"). Use this for IDPs that place\ngroups under a URI-style claim (e.g. \"https://example.com/groups\").\nWhen empty, only the well-known claim names are checked.", + "type": "string" + }, + "groupEntityType": { + "description": "GroupEntityType is the Cedar entity type name used for principal parent\nUIDs synthesised from JWT group/role claims. Defaults to \"THVGroup\" when\nempty. Must match the entity type used in EntitiesJSON for transitive\n`in` checks to resolve. Namespaced names (`Foo::Bar`) are not yet supported.", + "type": "string" + }, + "policies": { + "description": "Policies contains Cedar policy definitions (when Type = \"cedar\").", + "items": { + "type": "string" + }, + "type": "array" + }, + "primaryUpstreamProvider": { + "description": "PrimaryUpstreamProvider names the upstream IDP provider whose access\ntoken should be used as the source of JWT claims for Cedar evaluation.\nWhen empty, claims from the ToolHive-issued token are used.\nMust match an upstream provider name configured in the embedded auth server\n(e.g. \"default\", \"github\"). Only relevant when the embedded auth server is active.", + "type": "string" + }, + "roleClaimName": { + "description": "RoleClaimName is the JWT claim key that contains role membership for the\nprincipal. When set, the claim is extracted separately from GroupClaimName\nand both are mapped to the configured group entity type. When empty, no\nrole extraction is performed.", + "type": "string" + }, + "type": { + "description": "Type is the authz type: \"cedar\", \"none\"", + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "additionalProperties": false + }, + "oidc": { + "description": "OIDC contains OIDC configuration (when Type = \"oidc\").", + "properties": { + "audience": { + "description": "Audience is the required token audience.", + "type": "string" + }, + "clientId": { + "description": "ClientID is the OAuth client ID.", + "type": "string" + }, + "clientSecretEnv": { + "description": "ClientSecretEnv is the name of the environment variable containing the client secret.\nThis is the secure way to reference secrets - the actual secret value is never stored\nin configuration files, only the environment variable name.\nThe secret value will be resolved from this environment variable at runtime.", + "type": "string" + }, + "insecureAllowHttp": { + "description": "InsecureAllowHTTP allows HTTP (non-HTTPS) OIDC issuers for development/testing\nWARNING: This is insecure and should NEVER be used in production", + "type": "boolean" + }, + "introspectionUrl": { + "description": "IntrospectionURL is the token introspection endpoint URL (RFC 7662).\nWhen set, enables token introspection for opaque (non-JWT) tokens.", + "type": "string" + }, + "issuer": { + "description": "Issuer is the OIDC issuer URL.", + "pattern": "^https?://", + "type": "string" + }, + "jwksAllowPrivateIp": { + "description": "JwksAllowPrivateIP allows OIDC discovery and JWKS fetches to private IP addresses.\nEnable when the embedded auth server runs on a loopback address and\nthe OIDC middleware needs to fetch its JWKS from that address.\nUse with caution - only enable for trusted internal IDPs or testing.", + "type": "boolean" + }, + "jwksUrl": { + "description": "JWKSURL is the explicit JWKS endpoint URL.\nWhen set, skips OIDC discovery and fetches the JWKS directly from this URL.\nThis is useful when the OIDC issuer does not serve a /.well-known/openid-configuration.", + "type": "string" + }, + "protectedResourceAllowPrivateIp": { + "description": "ProtectedResourceAllowPrivateIP allows protected resource endpoint on private IP addresses\nUse with caution - only enable for trusted internal IDPs or testing", + "type": "boolean" + }, + "resource": { + "description": "Resource is the OAuth 2.0 resource indicator (RFC 8707).\nUsed in WWW-Authenticate header and OAuth discovery metadata (RFC 9728).\nIf not specified, defaults to Audience.", + "type": "string" + }, + "scopes": { + "description": "Scopes are the required OAuth scopes.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "audience", + "clientId", + "issuer" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type is the auth type: \"oidc\", \"local\", \"anonymous\"", + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "additionalProperties": false + }, + "metadata": { + "additionalProperties": { + "type": "string" + }, + "description": "Metadata stores additional configuration metadata.", + "type": "object" + }, + "name": { + "description": "Name is the virtual MCP server name.", + "type": "string" + }, + "operational": { + "description": "Operational configures operational settings.", + "properties": { + "failureHandling": { + "description": "FailureHandling configures failure handling behavior.", + "properties": { + "circuitBreaker": { + "description": "CircuitBreaker configures circuit breaker behavior.", + "properties": { + "enabled": { + "default": false, + "description": "Enabled controls whether circuit breaker is enabled.", + "type": "boolean" + }, + "failureThreshold": { + "default": 5, + "description": "FailureThreshold is the number of failures before opening the circuit.\nMust be >= 1.", + "minimum": 1, + "type": "integer" + }, + "timeout": { + "default": "60s", + "description": "Timeout is the duration to wait before attempting to close the circuit.\nMust be >= 1s to prevent thrashing.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string", + "x-kubernetes-validations": [ + { + "message": "timeout must be >= 1s", + "rule": "self == '' || duration(self) >= duration('1s')" + } + ] + } + }, + "type": "object", + "additionalProperties": false + }, + "healthCheckInterval": { + "default": "30s", + "description": "HealthCheckInterval is the interval between health checks.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "healthCheckTimeout": { + "default": "10s", + "description": "HealthCheckTimeout is the maximum duration for a single health check operation.\nShould be less than HealthCheckInterval to prevent checks from queuing up.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "partialFailureMode": { + "default": "fail", + "description": "PartialFailureMode defines behavior when some backends are unavailable.\n- fail: Fail entire request if any backend is unavailable\n- best_effort: Continue with available backends", + "enum": [ + "fail", + "best_effort" + ], + "type": "string" + }, + "statusReportingInterval": { + "default": "30s", + "description": "StatusReportingInterval is the interval for reporting status updates to Kubernetes.\nThis controls how often the vMCP runtime reports backend health and phase changes.\nLower values provide faster status updates but increase API server load.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "unhealthyThreshold": { + "default": 3, + "description": "UnhealthyThreshold is the number of consecutive failures before marking unhealthy.", + "type": "integer" + } + }, + "type": "object", + "additionalProperties": false + }, + "logLevel": { + "description": "LogLevel sets the logging level for the Virtual MCP server.\nThe only valid value is \"debug\" to enable debug logging.\nWhen omitted or empty, the server uses info level logging.", + "enum": [ + "debug" + ], + "type": "string" + }, + "timeouts": { + "description": "Timeouts configures timeout settings.", + "properties": { + "default": { + "default": "30s", + "description": "Default is the default timeout for backend requests.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "perWorkload": { + "additionalProperties": { + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "description": "PerWorkload defines per-workload timeout overrides.", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "optimizer": { + "description": "Optimizer configures the MCP optimizer for context optimization on large toolsets.\nWhen enabled, vMCP exposes only find_tool and call_tool operations to clients\ninstead of all backend tools directly. This reduces token usage by allowing\nLLMs to discover relevant tools on demand rather than receiving all tool definitions.", + "properties": { + "embeddingService": { + "description": "EmbeddingService is the full base URL of the embedding service endpoint\n(e.g., http://my-embedding.default.svc.cluster.local:8080) for semantic\ntool discovery.\n\nIn a Kubernetes environment, it is more convenient to use the\nVirtualMCPServerSpec.EmbeddingServerRef field instead of setting this\ndirectly. EmbeddingServerRef references an EmbeddingServer CRD by name,\nand the operator automatically resolves the referenced resource's\nStatus.URL to populate this field. This provides managed lifecycle\n(the operator watches the EmbeddingServer for readiness and URL changes)\nand avoids hardcoding service URLs in the config. If both\nEmbeddingServerRef and this field are set, EmbeddingServerRef takes\nprecedence and this value is overridden with a warning.", + "type": "string" + }, + "embeddingServiceTimeout": { + "default": "30s", + "description": "EmbeddingServiceTimeout is the HTTP request timeout for calls to the embedding service.\nDefaults to 30s if not specified.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "hybridSearchSemanticRatio": { + "description": "HybridSearchSemanticRatio controls the balance between semantic (meaning-based)\nand keyword search results. 0.0 = all keyword, 1.0 = all semantic.\nDefaults to \"0.5\" if not specified or empty.\nSerialized as a string because CRDs do not support float types portably.", + "pattern": "^([0-9]*[.])?[0-9]+$", + "type": "string" + }, + "maxToolsToReturn": { + "description": "MaxToolsToReturn is the maximum number of tool results returned by a search query.\nDefaults to 8 if not specified or zero.", + "maximum": 50, + "minimum": 1, + "type": "integer" + }, + "semanticDistanceThreshold": { + "description": "SemanticDistanceThreshold is the maximum distance for semantic search results.\nResults exceeding this threshold are filtered out from semantic search.\nThis threshold does not apply to keyword search.\nRange: 0 = identical, 2 = completely unrelated.\nDefaults to \"1.0\" if not specified or empty.\nSerialized as a string because CRDs do not support float types portably.", + "pattern": "^([0-9]*[.])?[0-9]+$", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "outgoingAuth": { + "description": "OutgoingAuth configures how the virtual MCP server authenticates to backends.\nWhen using the Kubernetes operator, this is populated by the converter from\nVirtualMCPServerSpec.OutgoingAuth and any values set here will be superseded.", + "properties": { + "backends": { + "additionalProperties": { + "description": "BackendAuthStrategy defines how to authenticate to a specific backend.\n\nThis struct provides type-safe configuration for different authentication strategies\nusing HeaderInjection or TokenExchange fields based on the Type field.", + "properties": { + "awsSts": { + "description": "AwsSts contains configuration for AWS STS auth strategy.\nUsed when Type = \"aws_sts\".", + "properties": { + "fallbackRoleArn": { + "description": "FallbackRoleArn is the IAM role ARN to assume when no role mappings match.", + "type": "string" + }, + "region": { + "description": "Region is the AWS region for the STS endpoint and service.", + "type": "string" + }, + "roleClaim": { + "description": "RoleClaim is the JWT claim to use for role mapping evaluation.", + "type": "string" + }, + "roleMappings": { + "description": "RoleMappings defines claim-based role selection rules.", + "items": { + "description": "RoleMapping defines a rule for mapping JWT claims to IAM roles.\nMappings are evaluated in priority order (lower number = higher priority).", + "properties": { + "claim": { + "description": "Claim is a simple claim value to match against the RoleClaim field.", + "type": "string" + }, + "matcher": { + "description": "Matcher is a CEL expression for complex matching against JWT claims.", + "type": "string" + }, + "priority": { + "description": "Priority determines evaluation order (lower values = higher priority).\nMirrors awssts.RoleMapping.Priority, which is *int because the role mapper\nuses math.MaxInt for nil-priority semantics in effectivePriority.", + "type": "integer" + }, + "roleArn": { + "description": "RoleArn is the IAM role ARN to assume when this mapping matches.", + "type": "string" + } + }, + "required": [ + "roleArn" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "service": { + "description": "Service is the AWS service name for SigV4 signing.", + "type": "string" + }, + "sessionDuration": { + "description": "SessionDuration is the duration in seconds for the STS session.", + "format": "int32", + "type": "integer" + }, + "sessionNameClaim": { + "description": "SessionNameClaim is the JWT claim to use for the role session name.", + "type": "string" + }, + "subjectProviderName": { + "description": "SubjectProviderName selects which upstream provider's token to use as the\nweb identity token for AssumeRoleWithWebIdentity. When set, the token is\nlooked up from Identity.UpstreamTokens instead of the request's\nAuthorization header.", + "type": "string" + } + }, + "required": [ + "region" + ], + "type": "object", + "additionalProperties": false + }, + "headerInjection": { + "description": "HeaderInjection contains configuration for header injection auth strategy.\nUsed when Type = \"header_injection\".", + "properties": { + "headerName": { + "description": "HeaderName is the name of the header to inject (e.g., \"Authorization\").", + "type": "string" + }, + "headerValue": { + "description": "HeaderValue is the static header value to inject.\nEither HeaderValue or HeaderValueEnv should be set, not both.", + "type": "string" + }, + "headerValueEnv": { + "description": "HeaderValueEnv is the environment variable name containing the header value.\nThe value will be resolved at runtime from this environment variable.\nEither HeaderValue or HeaderValueEnv should be set, not both.", + "type": "string" + } + }, + "required": [ + "headerName" + ], + "type": "object", + "additionalProperties": false + }, + "tokenExchange": { + "description": "TokenExchange contains configuration for token exchange auth strategy.\nUsed when Type = \"token_exchange\".", + "properties": { + "audience": { + "description": "Audience is the target audience for the exchanged token.", + "type": "string" + }, + "clientId": { + "description": "ClientID is the OAuth client ID for the token exchange request.", + "type": "string" + }, + "clientSecret": { + "description": "ClientSecret is the OAuth client secret (use ClientSecretEnv for security).", + "type": "string" + }, + "clientSecretEnv": { + "description": "ClientSecretEnv is the environment variable name containing the client secret.\nThe value will be resolved at runtime from this environment variable.", + "type": "string" + }, + "scopes": { + "description": "Scopes are the requested scopes for the exchanged token.", + "items": { + "type": "string" + }, + "type": "array" + }, + "subjectProviderName": { + "description": "SubjectProviderName selects which upstream provider's token to use as the\nsubject token. When set, the token is looked up from Identity.UpstreamTokens\ninstead of using Identity.Token.\nWhen left empty and an embedded authorization server is configured, the system\nautomatically populates this field with the first configured upstream provider name.\nSet it explicitly to override that default or to select a specific provider when\nmultiple upstreams are configured.", + "type": "string" + }, + "subjectTokenType": { + "description": "SubjectTokenType is the token type of the incoming subject token.\nDefaults to \"urn:ietf:params:oauth:token-type:access_token\" if not specified.", + "type": "string" + }, + "tokenUrl": { + "description": "TokenURL is the OAuth token endpoint URL for token exchange.", + "type": "string" + } + }, + "required": [ + "tokenUrl" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type is the auth strategy: \"unauthenticated\", \"header_injection\", \"token_exchange\", \"upstream_inject\", \"aws_sts\", \"obo\"", + "type": "string" + }, + "upstreamInject": { + "description": "UpstreamInject contains configuration for upstream inject auth strategy.\nUsed when Type = \"upstream_inject\".", + "properties": { + "providerName": { + "description": "ProviderName is the name of the upstream provider configured in the\nembedded authorization server. Must match an entry in AuthServer.Upstreams.", + "type": "string" + } + }, + "required": [ + "providerName" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "type" + ], + "type": "object", + "additionalProperties": false + }, + "description": "Backends contains per-backend auth configuration.", + "type": "object" + }, + "default": { + "description": "Default is the default auth strategy for backends without explicit config.", + "properties": { + "awsSts": { + "description": "AwsSts contains configuration for AWS STS auth strategy.\nUsed when Type = \"aws_sts\".", + "properties": { + "fallbackRoleArn": { + "description": "FallbackRoleArn is the IAM role ARN to assume when no role mappings match.", + "type": "string" + }, + "region": { + "description": "Region is the AWS region for the STS endpoint and service.", + "type": "string" + }, + "roleClaim": { + "description": "RoleClaim is the JWT claim to use for role mapping evaluation.", + "type": "string" + }, + "roleMappings": { + "description": "RoleMappings defines claim-based role selection rules.", + "items": { + "description": "RoleMapping defines a rule for mapping JWT claims to IAM roles.\nMappings are evaluated in priority order (lower number = higher priority).", + "properties": { + "claim": { + "description": "Claim is a simple claim value to match against the RoleClaim field.", + "type": "string" + }, + "matcher": { + "description": "Matcher is a CEL expression for complex matching against JWT claims.", + "type": "string" + }, + "priority": { + "description": "Priority determines evaluation order (lower values = higher priority).\nMirrors awssts.RoleMapping.Priority, which is *int because the role mapper\nuses math.MaxInt for nil-priority semantics in effectivePriority.", + "type": "integer" + }, + "roleArn": { + "description": "RoleArn is the IAM role ARN to assume when this mapping matches.", + "type": "string" + } + }, + "required": [ + "roleArn" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "service": { + "description": "Service is the AWS service name for SigV4 signing.", + "type": "string" + }, + "sessionDuration": { + "description": "SessionDuration is the duration in seconds for the STS session.", + "format": "int32", + "type": "integer" + }, + "sessionNameClaim": { + "description": "SessionNameClaim is the JWT claim to use for the role session name.", + "type": "string" + }, + "subjectProviderName": { + "description": "SubjectProviderName selects which upstream provider's token to use as the\nweb identity token for AssumeRoleWithWebIdentity. When set, the token is\nlooked up from Identity.UpstreamTokens instead of the request's\nAuthorization header.", + "type": "string" + } + }, + "required": [ + "region" + ], + "type": "object", + "additionalProperties": false + }, + "headerInjection": { + "description": "HeaderInjection contains configuration for header injection auth strategy.\nUsed when Type = \"header_injection\".", + "properties": { + "headerName": { + "description": "HeaderName is the name of the header to inject (e.g., \"Authorization\").", + "type": "string" + }, + "headerValue": { + "description": "HeaderValue is the static header value to inject.\nEither HeaderValue or HeaderValueEnv should be set, not both.", + "type": "string" + }, + "headerValueEnv": { + "description": "HeaderValueEnv is the environment variable name containing the header value.\nThe value will be resolved at runtime from this environment variable.\nEither HeaderValue or HeaderValueEnv should be set, not both.", + "type": "string" + } + }, + "required": [ + "headerName" + ], + "type": "object", + "additionalProperties": false + }, + "tokenExchange": { + "description": "TokenExchange contains configuration for token exchange auth strategy.\nUsed when Type = \"token_exchange\".", + "properties": { + "audience": { + "description": "Audience is the target audience for the exchanged token.", + "type": "string" + }, + "clientId": { + "description": "ClientID is the OAuth client ID for the token exchange request.", + "type": "string" + }, + "clientSecret": { + "description": "ClientSecret is the OAuth client secret (use ClientSecretEnv for security).", + "type": "string" + }, + "clientSecretEnv": { + "description": "ClientSecretEnv is the environment variable name containing the client secret.\nThe value will be resolved at runtime from this environment variable.", + "type": "string" + }, + "scopes": { + "description": "Scopes are the requested scopes for the exchanged token.", + "items": { + "type": "string" + }, + "type": "array" + }, + "subjectProviderName": { + "description": "SubjectProviderName selects which upstream provider's token to use as the\nsubject token. When set, the token is looked up from Identity.UpstreamTokens\ninstead of using Identity.Token.\nWhen left empty and an embedded authorization server is configured, the system\nautomatically populates this field with the first configured upstream provider name.\nSet it explicitly to override that default or to select a specific provider when\nmultiple upstreams are configured.", + "type": "string" + }, + "subjectTokenType": { + "description": "SubjectTokenType is the token type of the incoming subject token.\nDefaults to \"urn:ietf:params:oauth:token-type:access_token\" if not specified.", + "type": "string" + }, + "tokenUrl": { + "description": "TokenURL is the OAuth token endpoint URL for token exchange.", + "type": "string" + } + }, + "required": [ + "tokenUrl" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type is the auth strategy: \"unauthenticated\", \"header_injection\", \"token_exchange\", \"upstream_inject\", \"aws_sts\", \"obo\"", + "type": "string" + }, + "upstreamInject": { + "description": "UpstreamInject contains configuration for upstream inject auth strategy.\nUsed when Type = \"upstream_inject\".", + "properties": { + "providerName": { + "description": "ProviderName is the name of the upstream provider configured in the\nembedded authorization server. Must match an entry in AuthServer.Upstreams.", + "type": "string" + } + }, + "required": [ + "providerName" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "type" + ], + "type": "object", + "additionalProperties": false + }, + "source": { + "description": "Source defines how to discover backend auth: \"inline\", \"discovered\"\n- inline: Explicit configuration in OutgoingAuth\n- discovered: Auto-discover from backend MCPServer.externalAuthConfigRef (Kubernetes only)", + "type": "string" + } + }, + "required": [ + "source" + ], + "type": "object", + "additionalProperties": false + }, + "rateLimiting": { + "description": "RateLimiting defines rate limiting configuration for the Virtual MCP server.\nRequires Redis session storage to be configured for distributed rate limiting.", + "properties": { + "perUser": { + "description": "PerUser is a token bucket applied independently to each authenticated user\nat the server level. Requires authentication to be enabled.\nEach unique userID creates Redis keys that expire after 2x refillPeriod.\nMemory formula: unique_users_per_TTL_window * (1 + num_tools_with_per_user_limits) keys.", + "properties": { + "maxTokens": { + "description": "MaxTokens is the maximum number of tokens (bucket capacity).\nThis is also the burst size: the maximum number of requests that can be served\ninstantaneously before the bucket is depleted.", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "refillPeriod": { + "description": "RefillPeriod is the duration to fully refill the bucket from zero to maxTokens.\nThe effective refill rate is maxTokens / refillPeriod tokens per second.\nFormat: Go duration string (e.g., \"1m0s\", \"30s\", \"1h0m0s\").", + "type": "string" + } + }, + "required": [ + "maxTokens", + "refillPeriod" + ], + "type": "object", + "additionalProperties": false + }, + "shared": { + "description": "Shared is a token bucket shared across all users for the entire server.", + "properties": { + "maxTokens": { + "description": "MaxTokens is the maximum number of tokens (bucket capacity).\nThis is also the burst size: the maximum number of requests that can be served\ninstantaneously before the bucket is depleted.", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "refillPeriod": { + "description": "RefillPeriod is the duration to fully refill the bucket from zero to maxTokens.\nThe effective refill rate is maxTokens / refillPeriod tokens per second.\nFormat: Go duration string (e.g., \"1m0s\", \"30s\", \"1h0m0s\").", + "type": "string" + } + }, + "required": [ + "maxTokens", + "refillPeriod" + ], + "type": "object", + "additionalProperties": false + }, + "tools": { + "description": "Tools defines per-tool rate limit overrides.\nEach entry applies additional rate limits to calls targeting a specific tool name.\nA request must pass both the server-level limit and the per-tool limit.", + "items": { + "description": "ToolRateLimitConfig defines rate limits for a specific tool.\nAt least one of shared or perUser must be configured.", + "properties": { + "name": { + "description": "Name is the MCP tool name this limit applies to.", + "minLength": 1, + "type": "string" + }, + "perUser": { + "description": "PerUser token bucket configuration for this tool.", + "properties": { + "maxTokens": { + "description": "MaxTokens is the maximum number of tokens (bucket capacity).\nThis is also the burst size: the maximum number of requests that can be served\ninstantaneously before the bucket is depleted.", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "refillPeriod": { + "description": "RefillPeriod is the duration to fully refill the bucket from zero to maxTokens.\nThe effective refill rate is maxTokens / refillPeriod tokens per second.\nFormat: Go duration string (e.g., \"1m0s\", \"30s\", \"1h0m0s\").", + "type": "string" + } + }, + "required": [ + "maxTokens", + "refillPeriod" + ], + "type": "object", + "additionalProperties": false + }, + "shared": { + "description": "Shared token bucket for this specific tool.", + "properties": { + "maxTokens": { + "description": "MaxTokens is the maximum number of tokens (bucket capacity).\nThis is also the burst size: the maximum number of requests that can be served\ninstantaneously before the bucket is depleted.", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "refillPeriod": { + "description": "RefillPeriod is the duration to fully refill the bucket from zero to maxTokens.\nThe effective refill rate is maxTokens / refillPeriod tokens per second.\nFormat: Go duration string (e.g., \"1m0s\", \"30s\", \"1h0m0s\").", + "type": "string" + } + }, + "required": [ + "maxTokens", + "refillPeriod" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "name" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "at least one of shared or perUser must be configured", + "rule": "has(self.shared) || has(self.perUser)" + } + ], + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "type": "object", + "x-kubernetes-validations": [ + { + "message": "at least one of shared, perUser, or tools must be configured", + "rule": "has(self.shared) || has(self.perUser) || (has(self.tools) && size(self.tools) > 0)" + } + ], + "additionalProperties": false + }, + "sessionStorage": { + "description": "SessionStorage configures session storage for stateful horizontal scaling.\nWhen provider is \"redis\", the operator injects Redis connection parameters\n(address, db, keyPrefix) here. The Redis password is provided separately via\nthe THV_SESSION_REDIS_PASSWORD environment variable.", + "properties": { + "address": { + "description": "Address is the Redis server address (required when provider is redis).", + "type": "string" + }, + "db": { + "default": 0, + "description": "DB is the Redis database number.", + "format": "int32", + "minimum": 0, + "type": "integer" + }, + "keyPrefix": { + "description": "KeyPrefix is an optional prefix for all Redis keys used by ToolHive.", + "type": "string" + }, + "provider": { + "description": "Provider is the session storage backend type.", + "enum": [ + "memory", + "redis" + ], + "type": "string" + } + }, + "required": [ + "provider" + ], + "type": "object", + "additionalProperties": false + }, + "telemetry": { + "description": "Telemetry configures OpenTelemetry-based observability for the Virtual MCP server\nincluding distributed tracing, OTLP metrics export, and Prometheus metrics endpoint.\nDeprecated (Kubernetes operator only): When deploying via the operator, use\nVirtualMCPServer.spec.telemetryConfigRef to reference a shared MCPTelemetryConfig\nresource instead. This field remains valid for standalone (non-operator) deployments.", + "properties": { + "caCertPath": { + "description": "CACertPath is the file path to a CA certificate bundle for the OTLP endpoint.\nWhen set, the OTLP exporters use this CA to verify the collector's TLS certificate\ninstead of relying solely on the system CA pool.", + "type": "string" + }, + "customAttributes": { + "additionalProperties": { + "type": "string" + }, + "description": "CustomAttributes contains custom resource attributes to be added to all telemetry signals.\nThese are parsed from CLI flags (--otel-custom-attributes) or environment variables\n(OTEL_RESOURCE_ATTRIBUTES) as key=value pairs.", + "type": "object" + }, + "enablePrometheusMetricsPath": { + "default": false, + "description": "EnablePrometheusMetricsPath controls whether to expose Prometheus-style /metrics endpoint.\nThe metrics are served on the main transport port at /metrics.\nThis is separate from OTLP metrics which are sent to the Endpoint.", + "type": "boolean" + }, + "endpoint": { + "description": "Endpoint is the OTLP endpoint URL", + "type": "string" + }, + "environmentVariables": { + "description": "EnvironmentVariables is a list of environment variable names that should be\nincluded in telemetry spans as attributes. Only variables in this list will\nbe read from the host machine and included in spans for observability.\nExample: [\"NODE_ENV\", \"DEPLOYMENT_ENV\", \"SERVICE_VERSION\"]", + "items": { + "type": "string" + }, + "type": "array" + }, + "headers": { + "additionalProperties": { + "type": "string" + }, + "description": "Headers contains authentication headers for the OTLP endpoint.", + "type": "object" + }, + "insecure": { + "default": false, + "description": "Insecure indicates whether to use HTTP instead of HTTPS for the OTLP endpoint.", + "type": "boolean" + }, + "metricsEnabled": { + "default": false, + "description": "MetricsEnabled controls whether OTLP metrics are enabled.\nWhen false, OTLP metrics are not sent even if an endpoint is configured.\nThis is independent of EnablePrometheusMetricsPath.", + "type": "boolean" + }, + "samplingRate": { + "default": "0.05", + "description": "SamplingRate is the trace sampling rate (0.0-1.0) as a string.\nOnly used when TracingEnabled is true.\nExample: \"0.05\" for 5% sampling.", + "type": "string" + }, + "serviceName": { + "description": "ServiceName is the service name for telemetry.\nWhen omitted, defaults to the server name (e.g., VirtualMCPServer name).", + "type": "string" + }, + "serviceVersion": { + "description": "ServiceVersion is the service version for telemetry.\nWhen omitted, defaults to the ToolHive version.", + "type": "string" + }, + "tracingEnabled": { + "default": false, + "description": "TracingEnabled controls whether distributed tracing is enabled.\nWhen false, no tracer provider is created even if an endpoint is configured.", + "type": "boolean" + }, + "useLegacyAttributes": { + "default": true, + "description": "UseLegacyAttributes controls whether legacy (pre-MCP OTEL semconv) attribute names\nare emitted alongside the new standard attribute names. When true, spans include both\nold and new attribute names for backward compatibility with existing dashboards.\nCurrently defaults to true; this will change to false in a future release.", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "x-kubernetes-preserve-unknown-fields": true, + "additionalProperties": false + }, + "embeddingServerRef": { + "description": "EmbeddingServerRef references an existing EmbeddingServer resource by name.\nWhen the optimizer is enabled, this field is required to point to a ready EmbeddingServer\nthat provides embedding capabilities.\nThe referenced EmbeddingServer must exist in the same namespace and be ready.", + "properties": { + "name": { + "description": "Name is the name of the EmbeddingServer resource", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "groupRef": { + "description": "GroupRef references the MCPGroup that defines backend workloads.\nThe referenced MCPGroup must exist in the same namespace.", + "properties": { + "name": { + "description": "Name is the name of the MCPGroup resource in the same namespace", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "imagePullSecrets": { + "description": "ImagePullSecrets allows specifying image pull secrets for the vMCP workload.\nThese are applied to both the vMCP Deployment's PodSpec.ImagePullSecrets\nand to the operator-managed ServiceAccount the vMCP server runs as, so private\nimages are pullable through either path.\n\nMerge semantics with PodTemplateSpec:\nThe deployed PodSpec.ImagePullSecrets is the Kubernetes-native strategic-merge\nunion of this field and spec.podTemplateSpec.spec.imagePullSecrets, merged by\nthe patchStrategy:\"merge\" / patchMergeKey:\"name\" tags on corev1.PodSpec.\n - This field is rendered first as the controller-generated default.\n - spec.podTemplateSpec.spec.imagePullSecrets is then strategic-merge-patched\n on top, keyed by Name. Distinct names from the two sources are unioned in\n the resulting list; entries with the same Name are deduplicated and the\n PodTemplateSpec entry wins on overlap (user override).\n - Order in the resulting list is not guaranteed and should not be relied on:\n strategic merge by name is order-insensitive.\n - The operator-managed ServiceAccount's imagePullSecrets list is populated\n ONLY from this field. spec.podTemplateSpec.spec.imagePullSecrets does not\n reach the ServiceAccount because PodTemplateSpec has no notion of a\n ServiceAccount. To make a secret usable via the ServiceAccount path\n (e.g. for sidecars or init containers that pull images independently),\n list it here rather than under spec.podTemplateSpec.\n\nNote on cross-CRD consistency:\nMCPRegistry currently uses an atomic-replace strategy for its imagePullSecrets\n(the user-provided value replaces the controller-generated list rather than\nbeing merged on top). VirtualMCPServer follows the Kubernetes-native\nstrategic-merge-by-name behavior described above. Aligning the two is tracked\nas a separate follow-up; until then, manifests that set imagePullSecrets on\nboth CRDs will see different override behavior between them.", + "items": { + "description": "LocalObjectReference contains enough information to let you locate the\nreferenced object inside the same namespace.", + "properties": { + "name": { + "default": "", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + } + }, + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "incomingAuth": { + "description": "IncomingAuth configures authentication for clients connecting to the Virtual MCP server.\nMust be explicitly set - use \"anonymous\" type when no authentication is required.\nThis field takes precedence over config.IncomingAuth and should be preferred because it\nsupports Kubernetes-native secret references (SecretKeyRef, ConfigMapRef) for secure\ndynamic discovery of credentials, rather than requiring secrets to be embedded in config.", + "properties": { + "authzConfig": { + "description": "AuthzConfig defines authorization policy configuration\nReuses MCPServer authz patterns", + "properties": { + "configMap": { + "description": "ConfigMap references a ConfigMap containing authorization configuration\nOnly used when Type is \"configMap\"", + "properties": { + "key": { + "default": "authz.json", + "description": "Key is the key in the ConfigMap that contains the authorization configuration", + "type": "string" + }, + "name": { + "description": "Name is the name of the ConfigMap", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "groupClaimName": { + "description": "GroupClaimName is the JWT claim key that contains group membership for the\nprincipal. When set, takes priority over the well-known defaults\n(\"groups\", \"roles\", \"cognito:groups\"). Use this for IDPs that place\ngroups under a URI-style claim (e.g. \"https://example.com/groups\"). When\nType is \"configMap\", a group_claim_name entry in the referenced ConfigMap\nis overridden by this field if both are set.", + "maxLength": 253, + "type": "string" + }, + "groupEntityType": { + "description": "GroupEntityType is the Cedar entity type name used for principal parent\nUIDs synthesised from JWT group/role claims. Defaults to \"THVGroup\" when\nempty. Must match the entity type used in the static entity store for\ntransitive `in` checks (e.g. `ClaimGroup \u2192 PlatformRole`) to resolve.\nNamespaced names (`Foo::Bar`) are not yet supported. When Type is\n\"configMap\", a group_entity_type entry in the referenced ConfigMap is\noverridden by this field if both are set.", + "maxLength": 63, + "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", + "type": "string" + }, + "inline": { + "description": "Inline contains direct authorization configuration\nOnly used when Type is \"inline\"", + "properties": { + "entitiesJson": { + "default": "[]", + "description": "EntitiesJSON is a JSON string representing Cedar entities. Required when\ntransitive policies (e.g. `ClaimGroup \u2192 PlatformRole`) need a static\nentity store; defaults to \"[]\".", + "type": "string" + }, + "policies": { + "description": "Policies is a list of Cedar policy strings", + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "primaryUpstreamProvider": { + "description": "PrimaryUpstreamProvider names the upstream IDP whose access token's\nclaims Cedar should evaluate.\n\nDeprecated: on VirtualMCPServer this field has moved to\nspec.authServerConfig.primaryUpstreamProvider. The old location is\nstill read for one release for backward compatibility; the\nVirtualMCPServer controller emits an AuthzPrimaryUpstreamProviderDeprecated\nWarning event whenever it is consumed, and removal is planned for the\nrelease after the deprecation cycle.\n\nOn MCPServer and MCPRemoteProxy this field has always been a structural\nno-op (those CRDs do not run an embedded auth server). Setting it\ncontinues to surface the AuthzPrimaryUpstreamProviderIgnored advisory\ncondition; the deprecation does not change that behaviour.", + "maxLength": 63, + "minLength": 1, + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "type": "string" + } + }, + "required": [ + "policies" + ], + "type": "object", + "additionalProperties": false + }, + "roleClaimName": { + "description": "RoleClaimName is the JWT claim key that contains role membership for the\nprincipal. When set, the claim is extracted separately from GroupClaimName\nand both are mapped to the configured GroupEntityType. When Type is\n\"configMap\", a role_claim_name entry in the referenced ConfigMap is\noverridden by this field if both are set.", + "maxLength": 253, + "type": "string" + }, + "type": { + "default": "configMap", + "description": "Type is the type of authorization configuration", + "enum": [ + "configMap", + "inline" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "configMap must be set when type is 'configMap', and must not be set otherwise", + "rule": "self.type == 'configMap' ? has(self.configMap) : !has(self.configMap)" + }, + { + "message": "inline must be set when type is 'inline', and must not be set otherwise", + "rule": "self.type == 'inline' ? has(self.inline) : !has(self.inline)" + } + ], + "additionalProperties": false + }, + "oidcConfigRef": { + "description": "OIDCConfigRef references a shared MCPOIDCConfig resource for OIDC authentication.\nThe referenced MCPOIDCConfig must exist in the same namespace as this VirtualMCPServer.\nPer-server overrides (audience, scopes) are specified here; shared provider config\nlives in the MCPOIDCConfig resource.", + "properties": { + "audience": { + "description": "Audience is the expected audience for token validation.\nThis MUST be unique per server to prevent token replay attacks.", + "minLength": 1, + "type": "string" + }, + "name": { + "description": "Name is the name of the MCPOIDCConfig resource", + "minLength": 1, + "type": "string" + }, + "resourceUrl": { + "description": "ResourceURL is the public URL for OAuth protected resource metadata (RFC 9728).\nWhen the server is exposed via Ingress or gateway, set this to the external\nURL that MCP clients connect to. If not specified, defaults to the internal\nKubernetes service URL.", + "type": "string" + }, + "scopes": { + "description": "Scopes is the list of OAuth scopes to advertise in the well-known endpoint (RFC 9728).\nIf empty, defaults to [\"openid\"].", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "required": [ + "audience", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type defines the authentication type: anonymous or oidc\nWhen no authentication is required, explicitly set this to \"anonymous\"", + "enum": [ + "anonymous", + "oidc" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "spec.incomingAuth.oidcConfigRef is required when type is oidc", + "rule": "self.type == 'oidc' ? has(self.oidcConfigRef) : true" + } + ], + "additionalProperties": false + }, + "outgoingAuth": { + "description": "OutgoingAuth configures authentication from Virtual MCP to backend MCPServers.\nThis field takes precedence over config.OutgoingAuth and should be preferred because it\nsupports Kubernetes-native secret references (SecretKeyRef, ConfigMapRef) for secure\ndynamic discovery of credentials, rather than requiring secrets to be embedded in config.", + "properties": { + "backends": { + "additionalProperties": { + "description": "BackendAuthConfig defines authentication configuration for a backend MCPServer", + "properties": { + "externalAuthConfigRef": { + "description": "ExternalAuthConfigRef references an MCPExternalAuthConfig resource\nOnly used when Type is \"externalAuthConfigRef\"", + "properties": { + "name": { + "description": "Name is the name of the MCPExternalAuthConfig resource", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type defines the authentication type", + "enum": [ + "discovered", + "externalAuthConfigRef" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "additionalProperties": false + }, + "description": "Backends defines per-backend authentication overrides\nWorks in all modes (discovered, inline)", + "type": "object" + }, + "default": { + "description": "Default defines default behavior for backends without explicit auth config", + "properties": { + "externalAuthConfigRef": { + "description": "ExternalAuthConfigRef references an MCPExternalAuthConfig resource\nOnly used when Type is \"externalAuthConfigRef\"", + "properties": { + "name": { + "description": "Name is the name of the MCPExternalAuthConfig resource", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type defines the authentication type", + "enum": [ + "discovered", + "externalAuthConfigRef" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "additionalProperties": false + }, + "source": { + "default": "discovered", + "description": "Source defines how backend authentication configurations are determined\n- discovered: Automatically discover from backend's MCPServer.spec.externalAuthConfigRef\n- inline: Explicit per-backend configuration in VirtualMCPServer", + "enum": [ + "discovered", + "inline" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "podTemplateSpec": { + "description": "PodTemplateSpec defines the pod template to use for the Virtual MCP server\nThis allows for customizing the pod configuration beyond what is provided by the other fields.\nNote that to modify the specific container the Virtual MCP server runs in, you must specify\nthe 'vmcp' container name in the PodTemplateSpec.\nThis field accepts a PodTemplateSpec object as JSON/YAML.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "replicas": { + "description": "Replicas is the desired number of vMCP pod replicas.\nVirtualMCPServer creates a single Deployment for the vMCP aggregator process,\nso there is only one replicas field (unlike MCPServer which has separate\nReplicas and BackendReplicas for its two Deployments).\nWhen nil, the operator does not set Deployment.Spec.Replicas, leaving replica\nmanagement to an HPA or other external controller.", + "format": "int32", + "minimum": 0, + "type": "integer" + }, + "serviceAccount": { + "description": "ServiceAccount is the name of an already existing service account to use by the Virtual MCP server.\nIf not specified, a ServiceAccount will be created automatically and used by the Virtual MCP server.", + "type": "string" + }, + "serviceType": { + "default": "ClusterIP", + "description": "ServiceType specifies the Kubernetes service type for the Virtual MCP server", + "enum": [ + "ClusterIP", + "NodePort", + "LoadBalancer" + ], + "type": "string" + }, + "sessionAffinity": { + "default": "ClientIP", + "description": "SessionAffinity controls whether the Service routes repeated client connections to the same pod.\nMCP protocols (SSE, streamable-http) are stateful, so ClientIP is the default.\nSet to \"None\" for stateless servers or when using an external load balancer with its own affinity.", + "enum": [ + "ClientIP", + "None" + ], + "type": "string" + }, + "sessionStorage": { + "description": "SessionStorage configures session storage for stateful horizontal scaling.\nWhen nil, no session storage is configured.", + "properties": { + "address": { + "description": "Address is the Redis server address (required when provider is redis)", + "minLength": 1, + "type": "string" + }, + "db": { + "default": 0, + "description": "DB is the Redis database number", + "format": "int32", + "minimum": 0, + "type": "integer" + }, + "keyPrefix": { + "description": "KeyPrefix is an optional prefix for all Redis keys used by ToolHive", + "type": "string" + }, + "passwordRef": { + "description": "PasswordRef is a reference to a Secret key containing the Redis password", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "provider": { + "description": "Provider is the session storage backend type", + "enum": [ + "memory", + "redis" + ], + "type": "string" + } + }, + "required": [ + "provider" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "address is required", + "rule": "self.provider == 'redis' ? has(self.address) : true" + } + ], + "additionalProperties": false + }, + "telemetryConfigRef": { + "description": "TelemetryConfigRef references an MCPTelemetryConfig resource for shared telemetry configuration.\nThe referenced MCPTelemetryConfig must exist in the same namespace as this VirtualMCPServer.\nCross-namespace references are not supported for security and isolation reasons.", + "properties": { + "name": { + "description": "Name is the name of the MCPTelemetryConfig resource", + "minLength": 1, + "type": "string" + }, + "serviceName": { + "description": "ServiceName overrides the telemetry service name for this specific server.\nThis MUST be unique per server for proper observability (e.g., distinguishing\ntraces and metrics from different servers sharing the same collector).\nIf empty, defaults to the server name with \"thv-\" prefix at runtime.", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "groupRef", + "incomingAuth" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "config.rateLimiting requires sessionStorage with provider 'redis'", + "rule": "!has(self.config) || !has(self.config.rateLimiting) || (has(self.sessionStorage) && self.sessionStorage.provider == 'redis')" + }, + { + "message": "config.rateLimiting.perUser requires incomingAuth.type oidc", + "rule": "!(has(self.config) && has(self.config.rateLimiting) && has(self.config.rateLimiting.perUser)) || (has(self.incomingAuth) && self.incomingAuth.type == 'oidc')" + }, + { + "message": "per-tool perUser rate limiting requires incomingAuth.type oidc", + "rule": "!has(self.config) || !has(self.config.rateLimiting) || !has(self.config.rateLimiting.tools) || self.config.rateLimiting.tools.all(t, !has(t.perUser)) || (has(self.incomingAuth) && self.incomingAuth.type == 'oidc')" + } + ], + "additionalProperties": false + }, + "status": { + "description": "VirtualMCPServerStatus defines the observed state of VirtualMCPServer", + "properties": { + "backendCount": { + "description": "BackendCount is the number of routable backends (ready + unauthenticated).\nExcludes unavailable, degraded, and unknown backends.", + "format": "int32", + "type": "integer" + }, + "conditions": { + "description": "Conditions represent the latest available observations of the VirtualMCPServer's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "discoveredBackends": { + "description": "DiscoveredBackends lists discovered backend configurations from the MCPGroup", + "items": { + "description": "DiscoveredBackend represents a backend server discovered by vMCP runtime.\nThis type is shared with the Kubernetes operator CRD (VirtualMCPServer.Status.DiscoveredBackends).", + "properties": { + "authConfigRef": { + "description": "AuthConfigRef is the name of the discovered MCPExternalAuthConfig (if any)", + "type": "string" + }, + "authType": { + "description": "AuthType is the type of authentication configured", + "type": "string" + }, + "circuitBreakerState": { + "description": "CircuitBreakerState is the current circuit breaker state (closed, open, half-open).\nEmpty when circuit breaker is disabled or not configured.", + "enum": [ + "closed", + "open", + "half-open" + ], + "type": "string" + }, + "circuitLastChanged": { + "description": "CircuitLastChanged is the timestamp when the circuit breaker state last changed.\nEmpty when circuit breaker is disabled or has never changed state.", + "format": "date-time", + "type": "string" + }, + "consecutiveFailures": { + "description": "ConsecutiveFailures is the current count of consecutive health check failures.\nResets to 0 when the backend becomes healthy again.", + "type": "integer" + }, + "lastHealthCheck": { + "description": "LastHealthCheck is the timestamp of the last health check", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "Message provides additional information about the backend status", + "type": "string" + }, + "name": { + "description": "Name is the name of the backend MCPServer", + "type": "string" + }, + "status": { + "description": "Status is the current status of the backend (ready, degraded, unavailable, unauthenticated, unknown).\nUse BackendHealthStatus.ToCRDStatus() to populate this field.", + "type": "string" + }, + "url": { + "description": "URL is the URL of the backend MCPServer", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + }, + "message": { + "description": "Message provides additional information about the current phase", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration is the most recent generation observed for this VirtualMCPServer", + "format": "int64", + "type": "integer" + }, + "oidcConfigHash": { + "description": "OIDCConfigHash is the hash of the referenced MCPOIDCConfig spec for change detection.\nOnly populated when IncomingAuth.OIDCConfigRef is set.", + "type": "string" + }, + "phase": { + "default": "Pending", + "description": "Phase is the current phase of the VirtualMCPServer", + "enum": [ + "Pending", + "Ready", + "Degraded", + "Failed" + ], + "type": "string" + }, + "telemetryConfigHash": { + "description": "TelemetryConfigHash is the hash of the referenced MCPTelemetryConfig spec for change detection.\nOnly populated when TelemetryConfigRef is set.", + "type": "string" + }, + "url": { + "description": "URL is the URL where the Virtual MCP server can be accessed", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/mcpexternalauthconfig_v1alpha1.json b/crdSchemas/mcpexternalauthconfig_v1alpha1.json new file mode 100644 index 0000000..308902d --- /dev/null +++ b/crdSchemas/mcpexternalauthconfig_v1alpha1.json @@ -0,0 +1,1147 @@ +{ + "description": "MCPExternalAuthConfig is the deprecated v1alpha1 version of the MCPExternalAuthConfig resource.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPExternalAuthConfigSpec defines the desired state of MCPExternalAuthConfig.\nMCPExternalAuthConfig resources are namespace-scoped and can only be referenced by\nMCPServer resources in the same namespace.", + "properties": { + "awsSts": { + "description": "AWSSts configures AWS STS authentication with SigV4 request signing\nOnly used when Type is \"awsSts\"", + "properties": { + "fallbackRoleArn": { + "description": "FallbackRoleArn is the IAM role ARN to assume when no role mappings match\nUsed as the default role when RoleMappings is empty or no mapping matches\nAt least one of FallbackRoleArn or RoleMappings must be configured (enforced by webhook)", + "pattern": "^arn:(aws|aws-cn|aws-us-gov):iam::\\d{12}:role/[\\w+=,.@\\-_/]+$", + "type": "string" + }, + "region": { + "description": "Region is the AWS region for the STS endpoint and service (e.g., \"us-east-1\", \"eu-west-1\")", + "minLength": 1, + "pattern": "^[a-z]{2}(-[a-z]+)+-\\d+$", + "type": "string" + }, + "roleClaim": { + "default": "groups", + "description": "RoleClaim is the JWT claim to use for role mapping evaluation\nDefaults to \"groups\" to match common OIDC group claims", + "type": "string" + }, + "roleMappings": { + "description": "RoleMappings defines claim-based role selection rules\nAllows mapping JWT claims (e.g., groups, roles) to specific IAM roles\nLower priority values are evaluated first (higher priority)", + "items": { + "description": "RoleMapping defines a rule for mapping JWT claims to IAM roles.\nMappings are evaluated in priority order (lower number = higher priority), and the first\nmatching rule determines which IAM role to assume.\nExactly one of Claim or Matcher must be specified.", + "properties": { + "claim": { + "description": "Claim is a simple claim value to match against\nThe claim type is specified by AWSStsConfig.RoleClaim\nFor example, if RoleClaim is \"groups\", this would be a group name\nInternally compiled to a CEL expression: \"\" in claims[\"\"]\nMutually exclusive with Matcher", + "minLength": 1, + "type": "string" + }, + "matcher": { + "description": "Matcher is a CEL expression for complex matching against JWT claims\nThe expression has access to a \"claims\" variable containing all JWT claims as map[string]any\nExamples:\n - \"admins\" in claims[\"groups\"]\n - claims[\"sub\"] == \"user123\" && !(\"act\" in claims)\nMutually exclusive with Claim", + "minLength": 1, + "type": "string" + }, + "priority": { + "description": "Priority determines evaluation order (lower values = higher priority)\nAllows fine-grained control over role selection precedence\nWhen omitted, this mapping has the lowest possible priority and\nconfiguration order acts as tie-breaker via stable sort", + "format": "int32", + "minimum": 0, + "type": "integer" + }, + "roleArn": { + "description": "RoleArn is the IAM role ARN to assume when this mapping matches", + "pattern": "^arn:(aws|aws-cn|aws-us-gov):iam::\\d{12}:role/[\\w+=,.@\\-_/]+$", + "type": "string" + } + }, + "required": [ + "roleArn" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "service": { + "default": "aws-mcp", + "description": "Service is the AWS service name for SigV4 signing\nDefaults to \"aws-mcp\" for AWS MCP Server endpoints", + "type": "string" + }, + "sessionDuration": { + "default": 3600, + "description": "SessionDuration is the duration in seconds for the STS session\nMust be between 900 (15 minutes) and 43200 (12 hours)\nDefaults to 3600 (1 hour) if not specified", + "format": "int32", + "maximum": 43200, + "minimum": 900, + "type": "integer" + }, + "sessionNameClaim": { + "default": "sub", + "description": "SessionNameClaim is the JWT claim to use for role session name\nDefaults to \"sub\" to use the subject claim", + "type": "string" + }, + "subjectProviderName": { + "description": "SubjectProviderName is the name of the upstream provider whose access token\nis used as the web identity token for STS AssumeRoleWithWebIdentity.\nThis field is used exclusively by VirtualMCPServer, where there is no\nupstream swap middleware to replace the bearer token before the strategy runs.\nWhen left empty and an embedded authorization server is configured on the\nVirtualMCPServer, the controller automatically populates this field with\nthe first configured upstream provider name. Set it explicitly to override\nthat default or to select a specific provider when multiple upstreams are\nconfigured.\nWhen no embedded auth server is present, the bearer token from the incoming\nrequest's Authorization header is used instead.", + "type": "string" + } + }, + "required": [ + "region" + ], + "type": "object", + "additionalProperties": false + }, + "bearerToken": { + "description": "BearerToken configures bearer token authentication\nOnly used when Type is \"bearerToken\"", + "properties": { + "tokenSecretRef": { + "description": "TokenSecretRef references a Kubernetes Secret containing the bearer token", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "tokenSecretRef" + ], + "type": "object", + "additionalProperties": false + }, + "embeddedAuthServer": { + "description": "EmbeddedAuthServer configures an embedded OAuth2/OIDC authorization server\nOnly used when Type is \"embeddedAuthServer\"", + "properties": { + "authorizationEndpointBaseUrl": { + "description": "AuthorizationEndpointBaseURL overrides the base URL used for the authorization_endpoint\nin the OAuth discovery document. When set, the discovery document will advertise\n`{authorizationEndpointBaseUrl}/oauth/authorize` instead of `{issuer}/oauth/authorize`.\nAll other endpoints (token, registration, JWKS) remain derived from the issuer.\nThis is useful when the browser-facing authorization endpoint needs to be on a\ndifferent host than the issuer used for backend-to-backend calls.\nMust be a valid HTTPS URL (or HTTP for localhost) without query, fragment, or trailing slash.", + "pattern": "^https?://[^\\s?#]+[^/\\s?#]$", + "type": "string" + }, + "baselineClientScopes": { + "description": "BaselineClientScopes is a baseline set of OAuth 2.0 scopes guaranteed to be\nincluded in every client registration. The embedded auth server unions these\nscopes into the registered set returned by RFC 7591 Dynamic Client\nRegistration, so a client that narrows the `scope` field at /oauth/register\ncan still request the baseline scopes at /oauth/authorize. All values must\nbe present in the upstream-derived scopesSupported set; the auth server\nfails to start if any value is missing.\n\nSecurity: every client registered via /oauth/register will gain the\nability to request these scopes at /oauth/authorize, regardless of what\nthe client itself requested. Keep the baseline narrow (typically\n\"openid\" and \"offline_access\"). Adding a privileged scope here \u2014 e.g.\n\"admin:read\" \u2014 would grant it to every DCR-registered client, including\npublic clients like Claude Code, Cursor, and VS Code.", + "items": { + "minLength": 1, + "pattern": "^[\\x21\\x23-\\x5B\\x5D-\\x7E]+$", + "type": "string" + }, + "maxItems": 10, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "hmacSecretRefs": { + "description": "HMACSecretRefs references Kubernetes Secrets containing symmetric secrets for signing\nauthorization codes and refresh tokens (opaque tokens).\nCurrent secret must be at least 32 bytes and cryptographically random.\nSupports secret rotation via multiple entries (first is current, rest are for verification).\nIf not specified, an ephemeral secret will be auto-generated (development only -\nauth codes and refresh tokens will be invalid after restart).", + "items": { + "description": "SecretKeyRef is a reference to a key within a Secret", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "issuer": { + "description": "Issuer is the issuer identifier for this authorization server.\nThis will be included in the \"iss\" claim of issued tokens.\nMust be a valid HTTPS URL (or HTTP for localhost) without query, fragment, or trailing slash (per RFC 8414).", + "pattern": "^https?://[^\\s?#]+[^/\\s?#]$", + "type": "string" + }, + "primaryUpstreamProvider": { + "description": "PrimaryUpstreamProvider names the upstream IDP whose access token Cedar\nshould read claims from when authorising a request. Must match the name\nof one of the entries in UpstreamProviders. When empty, the controller\nauto-selects the first entry of UpstreamProviders.\n\nOnly meaningful on VirtualMCPServer, where multiple upstream providers\ncan be configured and Cedar needs to pick which token's claims to\nevaluate. The VirtualMCPServer controller validates this field against\nUpstreamProviders at admission and rejects unresolvable values.\n\nOn MCPServer and MCPRemoteProxy this field is structurally present (the\nEmbeddedAuthServerConfig struct is shared) but has no runtime effect:\nthose CRDs are restricted to a single upstream so there is no choice to\nmake. Setting it on those CRDs is silently ignored.", + "maxLength": 63, + "minLength": 1, + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "type": "string" + }, + "signingKeySecretRefs": { + "description": "SigningKeySecretRefs references Kubernetes Secrets containing signing keys for JWT operations.\nSupports key rotation by allowing multiple keys (oldest keys are used for verification only).\nIf not specified, an ephemeral signing key will be auto-generated (development only -\nJWTs will be invalid after restart).", + "items": { + "description": "SecretKeyRef is a reference to a key within a Secret", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "maxItems": 5, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "storage": { + "description": "Storage configures the storage backend for the embedded auth server.\nIf not specified, defaults to in-memory storage.", + "properties": { + "redis": { + "description": "Redis configures the Redis storage backend.\nRequired when type is \"redis\".", + "properties": { + "aclUserConfig": { + "description": "ACLUserConfig configures Redis ACL user authentication.", + "properties": { + "passwordSecretRef": { + "description": "PasswordSecretRef references a Secret containing the Redis ACL password.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "usernameSecretRef": { + "description": "UsernameSecretRef references a Secret containing the Redis ACL username.\nWhen omitted, connections use legacy password-only AUTH. Omit for managed\nRedis tiers that do not support ACL users (e.g. GCP Memorystore Basic/Standard\nHA, Azure Cache for Redis). Set for services that support ACL users (e.g. AWS\nElastiCache non-cluster with Redis 6+ RBAC).", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "passwordSecretRef" + ], + "type": "object", + "additionalProperties": false + }, + "addr": { + "description": "Addr is the Redis server address (host:port). Required for standalone and cluster modes.\nUse for managed Redis services that expose a single endpoint (GCP Memorystore basic tier,\nAWS ElastiCache without cluster mode, or cluster-mode services when clusterMode is true).\nMutually exclusive with sentinelConfig.", + "type": "string" + }, + "clusterMode": { + "description": "ClusterMode enables the Redis Cluster protocol. Set to true when addr points to a\nRedis Cluster discovery endpoint (e.g., GCP Memorystore Cluster, AWS ElastiCache\ncluster mode enabled). Requires addr to be set.", + "type": "boolean" + }, + "dialTimeout": { + "default": "5s", + "description": "DialTimeout is the timeout for establishing connections.\nFormat: Go duration string (e.g., \"5s\", \"1m\").", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "readTimeout": { + "default": "3s", + "description": "ReadTimeout is the timeout for socket reads.\nFormat: Go duration string (e.g., \"3s\", \"1m\").", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "sentinelConfig": { + "description": "SentinelConfig holds Redis Sentinel configuration.\nUse for self-managed Redis with Sentinel-based HA. Mutually exclusive with addr.", + "properties": { + "db": { + "default": 0, + "description": "DB is the Redis database number.", + "format": "int32", + "type": "integer" + }, + "masterName": { + "description": "MasterName is the name of the Redis master monitored by Sentinel.", + "type": "string" + }, + "sentinelAddrs": { + "description": "SentinelAddrs is a list of Sentinel host:port addresses.\nMutually exclusive with SentinelService.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "sentinelService": { + "description": "SentinelService enables automatic discovery from a Kubernetes Service.\nMutually exclusive with SentinelAddrs.", + "properties": { + "name": { + "description": "Name of the Sentinel Service.", + "type": "string" + }, + "namespace": { + "description": "Namespace of the Sentinel Service (defaults to same namespace).", + "type": "string" + }, + "port": { + "default": 26379, + "description": "Port of the Sentinel service.", + "format": "int32", + "type": "integer" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "masterName" + ], + "type": "object", + "additionalProperties": false + }, + "sentinelTls": { + "description": "SentinelTLS configures TLS for connections to Sentinel instances.\nOnly applies when sentinelConfig is set. Presence of this field enables TLS.", + "properties": { + "caCertSecretRef": { + "description": "CACertSecretRef references a Secret containing a PEM-encoded CA certificate\nfor verifying the server. When not specified, system root CAs are used.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "insecureSkipVerify": { + "description": "InsecureSkipVerify skips TLS certificate verification.\nUse when connecting to services with self-signed certificates.", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "tls": { + "description": "TLS configures TLS for connections to the Redis/Valkey master.\nPresence of this field enables TLS. Omit to use plaintext.", + "properties": { + "caCertSecretRef": { + "description": "CACertSecretRef references a Secret containing a PEM-encoded CA certificate\nfor verifying the server. When not specified, system root CAs are used.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "insecureSkipVerify": { + "description": "InsecureSkipVerify skips TLS certificate verification.\nUse when connecting to services with self-signed certificates.", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "writeTimeout": { + "default": "3s", + "description": "WriteTimeout is the timeout for socket writes.\nFormat: Go duration string (e.g., \"3s\", \"1m\").", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + } + }, + "required": [ + "aclUserConfig" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "exactly one of addr or sentinelConfig must be set", + "rule": "(has(self.addr) && self.addr.size() > 0) != has(self.sentinelConfig)" + }, + { + "message": "clusterMode requires addr to be set", + "rule": "!(has(self.clusterMode) && self.clusterMode) || (has(self.addr) && self.addr.size() > 0)" + } + ], + "additionalProperties": false + }, + "type": { + "default": "memory", + "description": "Type specifies the storage backend type.\nValid values: \"memory\" (default), \"redis\".", + "enum": [ + "memory", + "redis" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "tokenLifespans": { + "description": "TokenLifespans configures the duration that various tokens are valid.\nIf not specified, defaults are applied (access: 1h, refresh: 7d, authCode: 10m).", + "properties": { + "accessTokenLifespan": { + "description": "AccessTokenLifespan is the duration that access tokens are valid.\nFormat: Go duration string (e.g., \"1h\", \"30m\", \"24h\").\nIf empty, defaults to 1 hour.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "authCodeLifespan": { + "description": "AuthCodeLifespan is the duration that authorization codes are valid.\nFormat: Go duration string (e.g., \"10m\", \"5m\").\nIf empty, defaults to 10 minutes.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "refreshTokenLifespan": { + "description": "RefreshTokenLifespan is the duration that refresh tokens are valid.\nFormat: Go duration string (e.g., \"168h\", \"7d\" as \"168h\").\nIf empty, defaults to 7 days (168h).", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "upstreamProviders": { + "description": "UpstreamProviders configures connections to upstream Identity Providers.\nThe embedded auth server delegates authentication to these providers.\nMCPServer and MCPRemoteProxy support a single upstream; VirtualMCPServer supports multiple.", + "items": { + "description": "UpstreamProviderConfig defines configuration for an upstream Identity Provider.\n\nExactly one of OIDCConfig or OAuth2Config must be set and must match the\ndeclared Type: oidc-typed providers set OIDCConfig, oauth2-typed providers\nset OAuth2Config. The CEL rule below enforces the pairing at admission; the\nmatching Go-level check in validateUpstreamProvider provides defense-in-depth\nfor stored objects.\n\nThe rule is structured as a chain of equality checks ending in an explicit\n`false`, so adding a new UpstreamProviderType value without extending this\nrule fails admission instead of silently demanding the OAuth2 shape. When\nadding a new type, extend both this rule and validateUpstreamProvider.", + "properties": { + "name": { + "description": "Name uniquely identifies this upstream provider.\nUsed for routing decisions and session binding in multi-upstream scenarios.\nMust be lowercase alphanumeric with hyphens (DNS-label-like).", + "maxLength": 63, + "minLength": 1, + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "type": "string" + }, + "oauth2Config": { + "description": "OAuth2Config contains OAuth 2.0-specific configuration.\nRequired when Type is \"oauth2\", must be nil when Type is \"oidc\".", + "properties": { + "additionalAuthorizationParams": { + "additionalProperties": { + "type": "string" + }, + "description": "AdditionalAuthorizationParams are extra query parameters to include in\nauthorization requests sent to the upstream provider.\nThis is useful for providers that require custom parameters, such as\nGoogle's access_type=offline for obtaining refresh tokens.\nFramework-managed parameters (response_type, client_id, redirect_uri,\nscope, state, code_challenge, code_challenge_method, nonce) are not allowed.", + "maxProperties": 16, + "type": "object" + }, + "authorizationEndpoint": { + "description": "AuthorizationEndpoint is the URL for the OAuth authorization endpoint.", + "pattern": "^https?://.*$", + "type": "string" + }, + "clientId": { + "description": "ClientID is the OAuth 2.0 client identifier registered with the upstream IDP.\nMutually exclusive with DCRConfig: when DCRConfig is set, ClientID is obtained\nat runtime via RFC 7591 Dynamic Client Registration and must be left empty.", + "type": "string" + }, + "clientSecretRef": { + "description": "ClientSecretRef references a Kubernetes Secret containing the OAuth 2.0 client secret.\nOptional for public clients using PKCE instead of client secret.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "dcrConfig": { + "description": "DCRConfig enables RFC 7591 Dynamic Client Registration against the upstream\nauthorization server. When set, the client credentials are obtained at\nruntime rather than being pre-provisioned, and ClientID must be left empty.\nMutually exclusive with ClientID.", + "properties": { + "discoveryUrl": { + "description": "DiscoveryURL is the RFC 8414 / OIDC Discovery document URL. The resolver\nissues a single GET against this URL (no well-known-path fallback) and\nreads registration_endpoint, authorization_endpoint, token_endpoint,\ntoken_endpoint_auth_methods_supported, and scopes_supported from the\nresponse.\nMutually exclusive with RegistrationEndpoint.\nHTTPS is required because the registration endpoint resolved from this\ndocument carries the initial access token and the issued client_secret\n(RFC 7591 \u00a73, RFC 8414 \u00a73). MaxLength is a defensive size cap (etcd\nobject budget, regex evaluation cost) and matches the conventional URL\nlength cap.", + "maxLength": 2048, + "pattern": "^https://[^\\s?#]+[^/\\s?#]$", + "type": "string" + }, + "initialAccessTokenRef": { + "description": "InitialAccessTokenRef is an optional reference to a Kubernetes Secret\ncarrying an RFC 7591 \u00a73 initial access token. When set, the resolver\npresents the token value as a Bearer credential on the registration\nrequest. Mirrors the ClientSecretRef pattern.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "registrationEndpoint": { + "description": "RegistrationEndpoint is the RFC 7591 registration endpoint URL used\ndirectly, bypassing discovery. When using this field, the caller is\nexpected to also supply AuthorizationEndpoint, TokenEndpoint, and an\nexplicit Scopes list on the parent OAuth2UpstreamConfig.\nMutually exclusive with DiscoveryURL.\nHTTPS is required because the registration endpoint carries the initial\naccess token and the issued client_secret (RFC 7591 \u00a73, RFC 8414 \u00a73).\nMaxLength is a defensive size cap (etcd object budget, regex evaluation\ncost) and matches the conventional URL length cap.", + "maxLength": 2048, + "pattern": "^https://[^\\s?#]+[^/\\s?#]$", + "type": "string" + }, + "softwareId": { + "description": "SoftwareID is the RFC 7591 \"software_id\" registration metadata value,\nidentifying the client software independent of any particular\nregistration instance. Typically a UUID or short identifier.", + "maxLength": 255, + "type": "string" + }, + "softwareStatement": { + "description": "SoftwareStatement is the RFC 7591 \"software_statement\" JWT asserting\nmetadata about the client software, signed by a party the authorization\nserver trusts.\n\nStored inline on the CR. The JWT is signed but not encrypted, so its\ncontents are visible to anyone with get/list/watch on this resource and\nappear in etcd backups in plaintext. Treat the value as non-confidential\n(signed attestation, not a secret). Operators that rotate software\nstatements like bearer credentials should keep them at the authorization\nserver side and rely on the registration endpoint's initial access\ntoken (see InitialAccessTokenRef) instead of placing them on the CR.\n\nBounded to 16384 characters as a defensive size cap (etcd object\nbudget, regex evaluation cost). Real-world signed statements with\nembedded x5c certificate chains, JWKS keys, or OIDC-Federation\ntrust-framework metadata routinely exceed 4 KB.", + "maxLength": 16384, + "type": "string" + } + }, + "type": "object", + "x-kubernetes-validations": [ + { + "message": "exactly one of discoveryUrl or registrationEndpoint must be set", + "rule": "has(self.discoveryUrl) != has(self.registrationEndpoint)" + } + ], + "additionalProperties": false + }, + "identityFromToken": { + "description": "IdentityFromToken extracts user identity (subject, name, email) directly\nfrom the OAuth2 token-endpoint response body using gjson dot-notation paths.\nWhen set, the embedded auth server skips the userinfo HTTP call entirely\nand resolves identity from the token response. See IdentityFromTokenConfig\nfor trust-model and uniqueness considerations.", + "properties": { + "emailPath": { + "description": "EmailPath is the dot-notation path to the email address field in the token response.\nIf not specified or if the path does not resolve to a string, the email is omitted.\nOmit the field entirely rather than setting it to an empty string.", + "maxLength": 256, + "minLength": 1, + "type": "string" + }, + "namePath": { + "description": "NamePath is the dot-notation path to the display name field in the token response.\nIf not specified or if the path does not resolve to a string, the display name is omitted.\nOmit the field entirely rather than setting it to an empty string.", + "maxLength": 256, + "minLength": 1, + "type": "string" + }, + "subjectPath": { + "description": "SubjectPath is the dot-notation path to the subject (user ID) field in the token response.\nWarning: claims read from the token response are trusted only via TLS, not\ncryptographically verified; prefer OIDC ID tokens when verifiable claims are required.\nExample: \"authed_user.id\" for Slack (top-level token-response field). For providers\nwhose token response embeds the access token as a JWT (e.g. Snowflake), use the\n\"@upstreamjwt\" modifier to decode the payload, e.g. \"access_token|@upstreamjwt|sub\".\nThe \"@upstreamjwt\" modifier performs no signature verification either.", + "maxLength": 256, + "minLength": 1, + "type": "string" + } + }, + "required": [ + "subjectPath" + ], + "type": "object", + "additionalProperties": false + }, + "redirectUri": { + "description": "RedirectURI is the callback URL where the upstream IDP will redirect after authentication.\nWhen not specified, defaults to `{resourceUrl}/oauth/callback` where `resourceUrl` is the\nURL associated with the resource (e.g., MCPServer or vMCP) using this config.", + "type": "string" + }, + "scopes": { + "description": "Scopes are the OAuth scopes to request from the upstream IDP.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "tokenEndpoint": { + "description": "TokenEndpoint is the URL for the OAuth token endpoint.", + "pattern": "^https?://.*$", + "type": "string" + }, + "tokenResponseMapping": { + "description": "TokenResponseMapping configures custom field extraction from non-standard token responses.\nSome OAuth providers (e.g., GovSlack) nest token fields under non-standard paths\ninstead of returning them at the top level. When set, ToolHive performs the token\nexchange HTTP call directly and extracts fields using the configured dot-notation paths.\nIf nil, standard OAuth 2.0 token response parsing is used.\nFor extracting user identity from the token response, see IdentityFromToken.", + "properties": { + "accessTokenPath": { + "description": "AccessTokenPath is the dot-notation path to the access token in the response.\nExample: \"authed_user.access_token\"", + "minLength": 1, + "type": "string" + }, + "expiresInPath": { + "description": "ExpiresInPath is the dot-notation path to the expires_in value (in seconds).\nIf not specified, defaults to \"expires_in\".", + "type": "string" + }, + "refreshTokenPath": { + "description": "RefreshTokenPath is the dot-notation path to the refresh token in the response.\nIf not specified, defaults to \"refresh_token\".", + "type": "string" + }, + "scopePath": { + "description": "ScopePath is the dot-notation path to the scope string in the response.\nIf not specified, defaults to \"scope\".", + "type": "string" + } + }, + "required": [ + "accessTokenPath" + ], + "type": "object", + "additionalProperties": false + }, + "userInfo": { + "description": "UserInfo contains configuration for fetching user information from the upstream provider.\nWhen omitted and IdentityFromToken is also unset, the embedded auth server runs in\nsynthesis mode for this upstream: a non-PII subject derived from the access token, no\nName/Email. Use this shape for upstreams with no userinfo surface and no identity in\nthe token response (e.g., MCP authorization servers per the MCP spec). When\nIdentityFromToken is set instead, identity is resolved from the token response body\n(e.g., Snowflake's \"username\" field, Slack's \"authed_user.id\"); the userinfo HTTP call\nis skipped entirely.", + "properties": { + "additionalHeaders": { + "additionalProperties": { + "type": "string" + }, + "description": "AdditionalHeaders contains extra headers to include in the userinfo request.\nUseful for providers that require specific headers (e.g., GitHub's Accept header).", + "type": "object" + }, + "endpointUrl": { + "description": "EndpointURL is the URL of the userinfo endpoint.", + "pattern": "^https?://.*$", + "type": "string" + }, + "fieldMapping": { + "description": "FieldMapping contains custom field mapping configuration for non-standard providers.\nIf nil, standard OIDC field names are used (\"sub\", \"name\", \"email\").", + "properties": { + "emailFields": { + "description": "EmailFields is an ordered list of field names to try for the email address.\nThe first non-empty value found will be used.\nDefault: [\"email\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "nameFields": { + "description": "NameFields is an ordered list of field names to try for the display name.\nThe first non-empty value found will be used.\nDefault: [\"name\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "subjectFields": { + "description": "SubjectFields is an ordered list of field names to try for the user ID.\nThe first non-empty value found will be used.\nDefault: [\"sub\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "type": "object", + "additionalProperties": false + }, + "httpMethod": { + "description": "HTTPMethod is the HTTP method to use for the userinfo request.\nIf not specified, defaults to GET.", + "enum": [ + "GET", + "POST" + ], + "type": "string" + } + }, + "required": [ + "endpointUrl" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "authorizationEndpoint", + "tokenEndpoint" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "exactly one of clientId or dcrConfig must be set", + "rule": "(has(self.clientId) && size(self.clientId) > 0) ? !has(self.dcrConfig) : has(self.dcrConfig)" + }, + { + "message": "clientSecretRef must not be set when dcrConfig is set; the client_secret is obtained at runtime via Dynamic Client Registration", + "rule": "!(has(self.dcrConfig) && has(self.clientSecretRef))" + } + ], + "additionalProperties": false + }, + "oidcConfig": { + "description": "OIDCConfig contains OIDC-specific configuration.\nRequired when Type is \"oidc\", must be nil when Type is \"oauth2\".", + "properties": { + "additionalAuthorizationParams": { + "additionalProperties": { + "type": "string" + }, + "description": "AdditionalAuthorizationParams are extra query parameters to include in\nauthorization requests sent to the upstream provider.\nThis is useful for providers that require custom parameters, such as\nGoogle's access_type=offline for obtaining refresh tokens.\nNote: when using access_type=offline, also set explicit scopes to avoid\nthe default offline_access scope being sent alongside it.\nFramework-managed parameters (response_type, client_id, redirect_uri,\nscope, state, code_challenge, code_challenge_method, nonce) are not allowed.", + "maxProperties": 16, + "type": "object" + }, + "clientId": { + "description": "ClientID is the OAuth 2.0 client identifier registered with the upstream IDP.", + "type": "string" + }, + "clientSecretRef": { + "description": "ClientSecretRef references a Kubernetes Secret containing the OAuth 2.0 client secret.\nOptional for public clients using PKCE instead of client secret.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "issuerUrl": { + "description": "IssuerURL is the OIDC issuer URL for automatic endpoint discovery.\nMust be a valid HTTPS URL.", + "pattern": "^https://.*$", + "type": "string" + }, + "redirectUri": { + "description": "RedirectURI is the callback URL where the upstream IDP will redirect after authentication.\nWhen not specified, defaults to `{resourceUrl}/oauth/callback` where `resourceUrl` is the\nURL associated with the resource (e.g., MCPServer or vMCP) using this config.", + "type": "string" + }, + "scopes": { + "description": "Scopes are the OAuth scopes to request from the upstream IDP.\nIf not specified, defaults to [\"openid\", \"offline_access\"].\nWhen using additionalAuthorizationParams with provider-specific refresh token\nmechanisms (e.g., Google's access_type=offline), set explicit scopes to avoid\nsending both offline_access and the provider-specific parameter.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "userInfoOverride": { + "description": "UserInfoOverride allows customizing UserInfo fetching behavior for OIDC providers.\nBy default, the UserInfo endpoint is discovered automatically via OIDC discovery.\nUse this to override the endpoint URL, HTTP method, or field mappings for providers\nthat return non-standard claim names in their UserInfo response.", + "properties": { + "additionalHeaders": { + "additionalProperties": { + "type": "string" + }, + "description": "AdditionalHeaders contains extra headers to include in the userinfo request.\nUseful for providers that require specific headers (e.g., GitHub's Accept header).", + "type": "object" + }, + "endpointUrl": { + "description": "EndpointURL is the URL of the userinfo endpoint.", + "pattern": "^https?://.*$", + "type": "string" + }, + "fieldMapping": { + "description": "FieldMapping contains custom field mapping configuration for non-standard providers.\nIf nil, standard OIDC field names are used (\"sub\", \"name\", \"email\").", + "properties": { + "emailFields": { + "description": "EmailFields is an ordered list of field names to try for the email address.\nThe first non-empty value found will be used.\nDefault: [\"email\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "nameFields": { + "description": "NameFields is an ordered list of field names to try for the display name.\nThe first non-empty value found will be used.\nDefault: [\"name\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "subjectFields": { + "description": "SubjectFields is an ordered list of field names to try for the user ID.\nThe first non-empty value found will be used.\nDefault: [\"sub\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "type": "object", + "additionalProperties": false + }, + "httpMethod": { + "description": "HTTPMethod is the HTTP method to use for the userinfo request.\nIf not specified, defaults to GET.", + "enum": [ + "GET", + "POST" + ], + "type": "string" + } + }, + "required": [ + "endpointUrl" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "clientId", + "issuerUrl" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type specifies the provider type: \"oidc\" or \"oauth2\"", + "enum": [ + "oidc", + "oauth2" + ], + "type": "string" + } + }, + "required": [ + "name", + "type" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "type must be 'oidc' or 'oauth2'; oidcConfig must be set when type is 'oidc' and oauth2Config must be set when type is 'oauth2' (and the other must not be set)", + "rule": "self.type == 'oidc' ? (has(self.oidcConfig) && !has(self.oauth2Config)) : self.type == 'oauth2' ? (has(self.oauth2Config) && !has(self.oidcConfig)) : false" + } + ], + "additionalProperties": false + }, + "minItems": 1, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "required": [ + "issuer", + "upstreamProviders" + ], + "type": "object", + "additionalProperties": false + }, + "headerInjection": { + "description": "HeaderInjection configures custom HTTP header injection\nOnly used when Type is \"headerInjection\"", + "properties": { + "headerName": { + "description": "HeaderName is the name of the HTTP header to inject", + "minLength": 1, + "type": "string" + }, + "valueSecretRef": { + "description": "ValueSecretRef references a Kubernetes Secret containing the header value", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "headerName", + "valueSecretRef" + ], + "type": "object", + "additionalProperties": false + }, + "tokenExchange": { + "description": "TokenExchange configures RFC-8693 OAuth 2.0 Token Exchange\nOnly used when Type is \"tokenExchange\"", + "properties": { + "audience": { + "description": "Audience is the target audience for the exchanged token", + "type": "string" + }, + "clientId": { + "description": "ClientID is the OAuth 2.0 client identifier\nOptional for some token exchange flows (e.g., Google Cloud Workforce Identity)", + "type": "string" + }, + "clientSecretRef": { + "description": "ClientSecretRef is a reference to a secret containing the OAuth 2.0 client secret\nOptional for some token exchange flows (e.g., Google Cloud Workforce Identity)", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "externalTokenHeaderName": { + "description": "ExternalTokenHeaderName is the name of the custom header to use for the exchanged token.\nIf set, the exchanged token will be added to this custom header (e.g., \"X-Upstream-Token\").\nIf empty or not set, the exchanged token will replace the Authorization header (default behavior).", + "type": "string" + }, + "scopes": { + "description": "Scopes is a list of OAuth 2.0 scopes to request for the exchanged token", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "subjectProviderName": { + "description": "SubjectProviderName is the name of the upstream provider whose token is used as the\nRFC 8693 subject token instead of identity.Token when performing token exchange.\nWhen left empty and an embedded authorization server is configured on the VirtualMCPServer,\nthe controller automatically populates this field with the first configured upstream\nprovider name. Set it explicitly to override that default or to select a specific\nprovider when multiple upstreams are configured.", + "type": "string" + }, + "subjectTokenType": { + "description": "SubjectTokenType is the type of the incoming subject token.\nAccepts short forms: \"access_token\" (default), \"id_token\", \"jwt\"\nOr full URNs: \"urn:ietf:params:oauth:token-type:access_token\",\n \"urn:ietf:params:oauth:token-type:id_token\",\n \"urn:ietf:params:oauth:token-type:jwt\"\nFor Google Workload Identity Federation with OIDC providers (like Okta), use \"id_token\"", + "pattern": "^(access_token|id_token|jwt|urn:ietf:params:oauth:token-type:(access_token|id_token|jwt))?$", + "type": "string" + }, + "tokenUrl": { + "description": "TokenURL is the OAuth 2.0 token endpoint URL for token exchange", + "type": "string" + } + }, + "required": [ + "audience", + "tokenUrl" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type is the type of external authentication to configure", + "enum": [ + "tokenExchange", + "headerInjection", + "bearerToken", + "unauthenticated", + "embeddedAuthServer", + "awsSts", + "upstreamInject" + ], + "type": "string" + }, + "upstreamInject": { + "description": "UpstreamInject configures upstream token injection for backend requests.\nOnly used when Type is \"upstreamInject\".", + "properties": { + "providerName": { + "description": "ProviderName is the name of the upstream IDP provider whose access token\nshould be injected as the Authorization: Bearer header.", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "providerName" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "type" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "tokenExchange configuration must be set if and only if type is 'tokenExchange'", + "rule": "self.type == 'tokenExchange' ? has(self.tokenExchange) : !has(self.tokenExchange)" + }, + { + "message": "headerInjection configuration must be set if and only if type is 'headerInjection'", + "rule": "self.type == 'headerInjection' ? has(self.headerInjection) : !has(self.headerInjection)" + }, + { + "message": "bearerToken configuration must be set if and only if type is 'bearerToken'", + "rule": "self.type == 'bearerToken' ? has(self.bearerToken) : !has(self.bearerToken)" + }, + { + "message": "embeddedAuthServer configuration must be set if and only if type is 'embeddedAuthServer'", + "rule": "self.type == 'embeddedAuthServer' ? has(self.embeddedAuthServer) : !has(self.embeddedAuthServer)" + }, + { + "message": "awsSts configuration must be set if and only if type is 'awsSts'", + "rule": "self.type == 'awsSts' ? has(self.awsSts) : !has(self.awsSts)" + }, + { + "message": "upstreamInject configuration must be set if and only if type is 'upstreamInject'", + "rule": "self.type == 'upstreamInject' ? has(self.upstreamInject) : !has(self.upstreamInject)" + }, + { + "message": "no configuration must be set when type is 'unauthenticated'", + "rule": "self.type == 'unauthenticated' ? (!has(self.tokenExchange) && !has(self.headerInjection) && !has(self.bearerToken) && !has(self.embeddedAuthServer) && !has(self.awsSts) && !has(self.upstreamInject)) : true" + } + ], + "additionalProperties": false + }, + "status": { + "description": "MCPExternalAuthConfigStatus defines the observed state of MCPExternalAuthConfig", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations of the MCPExternalAuthConfig's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "configHash": { + "description": "ConfigHash is a hash of the current configuration for change detection", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration is the most recent generation observed for this MCPExternalAuthConfig.\nIt corresponds to the MCPExternalAuthConfig's generation, which is updated on mutation by the API Server.", + "format": "int64", + "type": "integer" + }, + "referenceCount": { + "description": "ReferenceCount is the number of workloads referencing this config.", + "format": "int32", + "type": "integer" + }, + "referencingWorkloads": { + "description": "ReferencingWorkloads is a list of workload resources that reference this MCPExternalAuthConfig.\nEach entry identifies the workload by kind and name.", + "items": { + "description": "WorkloadReference identifies a workload that references a shared configuration resource.\nNamespace is implicit \u2014 cross-namespace references are not supported.", + "properties": { + "kind": { + "description": "Kind is the type of workload resource", + "enum": [ + "MCPServer", + "VirtualMCPServer", + "MCPRemoteProxy" + ], + "type": "string" + }, + "name": { + "description": "Name is the name of the workload resource", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/mcpexternalauthconfig_v1beta1.json b/crdSchemas/mcpexternalauthconfig_v1beta1.json new file mode 100644 index 0000000..e9639a0 --- /dev/null +++ b/crdSchemas/mcpexternalauthconfig_v1beta1.json @@ -0,0 +1,1147 @@ +{ + "description": "MCPExternalAuthConfig is the Schema for the mcpexternalauthconfigs API.\nMCPExternalAuthConfig resources are namespace-scoped and can only be referenced by\nMCPServer resources within the same namespace. Cross-namespace references\nare not supported for security and isolation reasons.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPExternalAuthConfigSpec defines the desired state of MCPExternalAuthConfig.\nMCPExternalAuthConfig resources are namespace-scoped and can only be referenced by\nMCPServer resources in the same namespace.", + "properties": { + "awsSts": { + "description": "AWSSts configures AWS STS authentication with SigV4 request signing\nOnly used when Type is \"awsSts\"", + "properties": { + "fallbackRoleArn": { + "description": "FallbackRoleArn is the IAM role ARN to assume when no role mappings match\nUsed as the default role when RoleMappings is empty or no mapping matches\nAt least one of FallbackRoleArn or RoleMappings must be configured (enforced by webhook)", + "pattern": "^arn:(aws|aws-cn|aws-us-gov):iam::\\d{12}:role/[\\w+=,.@\\-_/]+$", + "type": "string" + }, + "region": { + "description": "Region is the AWS region for the STS endpoint and service (e.g., \"us-east-1\", \"eu-west-1\")", + "minLength": 1, + "pattern": "^[a-z]{2}(-[a-z]+)+-\\d+$", + "type": "string" + }, + "roleClaim": { + "default": "groups", + "description": "RoleClaim is the JWT claim to use for role mapping evaluation\nDefaults to \"groups\" to match common OIDC group claims", + "type": "string" + }, + "roleMappings": { + "description": "RoleMappings defines claim-based role selection rules\nAllows mapping JWT claims (e.g., groups, roles) to specific IAM roles\nLower priority values are evaluated first (higher priority)", + "items": { + "description": "RoleMapping defines a rule for mapping JWT claims to IAM roles.\nMappings are evaluated in priority order (lower number = higher priority), and the first\nmatching rule determines which IAM role to assume.\nExactly one of Claim or Matcher must be specified.", + "properties": { + "claim": { + "description": "Claim is a simple claim value to match against\nThe claim type is specified by AWSStsConfig.RoleClaim\nFor example, if RoleClaim is \"groups\", this would be a group name\nInternally compiled to a CEL expression: \"\" in claims[\"\"]\nMutually exclusive with Matcher", + "minLength": 1, + "type": "string" + }, + "matcher": { + "description": "Matcher is a CEL expression for complex matching against JWT claims\nThe expression has access to a \"claims\" variable containing all JWT claims as map[string]any\nExamples:\n - \"admins\" in claims[\"groups\"]\n - claims[\"sub\"] == \"user123\" && !(\"act\" in claims)\nMutually exclusive with Claim", + "minLength": 1, + "type": "string" + }, + "priority": { + "description": "Priority determines evaluation order (lower values = higher priority)\nAllows fine-grained control over role selection precedence\nWhen omitted, this mapping has the lowest possible priority and\nconfiguration order acts as tie-breaker via stable sort", + "format": "int32", + "minimum": 0, + "type": "integer" + }, + "roleArn": { + "description": "RoleArn is the IAM role ARN to assume when this mapping matches", + "pattern": "^arn:(aws|aws-cn|aws-us-gov):iam::\\d{12}:role/[\\w+=,.@\\-_/]+$", + "type": "string" + } + }, + "required": [ + "roleArn" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "service": { + "default": "aws-mcp", + "description": "Service is the AWS service name for SigV4 signing\nDefaults to \"aws-mcp\" for AWS MCP Server endpoints", + "type": "string" + }, + "sessionDuration": { + "default": 3600, + "description": "SessionDuration is the duration in seconds for the STS session\nMust be between 900 (15 minutes) and 43200 (12 hours)\nDefaults to 3600 (1 hour) if not specified", + "format": "int32", + "maximum": 43200, + "minimum": 900, + "type": "integer" + }, + "sessionNameClaim": { + "default": "sub", + "description": "SessionNameClaim is the JWT claim to use for role session name\nDefaults to \"sub\" to use the subject claim", + "type": "string" + }, + "subjectProviderName": { + "description": "SubjectProviderName is the name of the upstream provider whose access token\nis used as the web identity token for STS AssumeRoleWithWebIdentity.\nThis field is used exclusively by VirtualMCPServer, where there is no\nupstream swap middleware to replace the bearer token before the strategy runs.\nWhen left empty and an embedded authorization server is configured on the\nVirtualMCPServer, the controller automatically populates this field with\nthe first configured upstream provider name. Set it explicitly to override\nthat default or to select a specific provider when multiple upstreams are\nconfigured.\nWhen no embedded auth server is present, the bearer token from the incoming\nrequest's Authorization header is used instead.", + "type": "string" + } + }, + "required": [ + "region" + ], + "type": "object", + "additionalProperties": false + }, + "bearerToken": { + "description": "BearerToken configures bearer token authentication\nOnly used when Type is \"bearerToken\"", + "properties": { + "tokenSecretRef": { + "description": "TokenSecretRef references a Kubernetes Secret containing the bearer token", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "tokenSecretRef" + ], + "type": "object", + "additionalProperties": false + }, + "embeddedAuthServer": { + "description": "EmbeddedAuthServer configures an embedded OAuth2/OIDC authorization server\nOnly used when Type is \"embeddedAuthServer\"", + "properties": { + "authorizationEndpointBaseUrl": { + "description": "AuthorizationEndpointBaseURL overrides the base URL used for the authorization_endpoint\nin the OAuth discovery document. When set, the discovery document will advertise\n`{authorizationEndpointBaseUrl}/oauth/authorize` instead of `{issuer}/oauth/authorize`.\nAll other endpoints (token, registration, JWKS) remain derived from the issuer.\nThis is useful when the browser-facing authorization endpoint needs to be on a\ndifferent host than the issuer used for backend-to-backend calls.\nMust be a valid HTTPS URL (or HTTP for localhost) without query, fragment, or trailing slash.", + "pattern": "^https?://[^\\s?#]+[^/\\s?#]$", + "type": "string" + }, + "baselineClientScopes": { + "description": "BaselineClientScopes is a baseline set of OAuth 2.0 scopes guaranteed to be\nincluded in every client registration. The embedded auth server unions these\nscopes into the registered set returned by RFC 7591 Dynamic Client\nRegistration, so a client that narrows the `scope` field at /oauth/register\ncan still request the baseline scopes at /oauth/authorize. All values must\nbe present in the upstream-derived scopesSupported set; the auth server\nfails to start if any value is missing.\n\nSecurity: every client registered via /oauth/register will gain the\nability to request these scopes at /oauth/authorize, regardless of what\nthe client itself requested. Keep the baseline narrow (typically\n\"openid\" and \"offline_access\"). Adding a privileged scope here \u2014 e.g.\n\"admin:read\" \u2014 would grant it to every DCR-registered client, including\npublic clients like Claude Code, Cursor, and VS Code.", + "items": { + "minLength": 1, + "pattern": "^[\\x21\\x23-\\x5B\\x5D-\\x7E]+$", + "type": "string" + }, + "maxItems": 10, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "hmacSecretRefs": { + "description": "HMACSecretRefs references Kubernetes Secrets containing symmetric secrets for signing\nauthorization codes and refresh tokens (opaque tokens).\nCurrent secret must be at least 32 bytes and cryptographically random.\nSupports secret rotation via multiple entries (first is current, rest are for verification).\nIf not specified, an ephemeral secret will be auto-generated (development only -\nauth codes and refresh tokens will be invalid after restart).", + "items": { + "description": "SecretKeyRef is a reference to a key within a Secret", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "issuer": { + "description": "Issuer is the issuer identifier for this authorization server.\nThis will be included in the \"iss\" claim of issued tokens.\nMust be a valid HTTPS URL (or HTTP for localhost) without query, fragment, or trailing slash (per RFC 8414).", + "pattern": "^https?://[^\\s?#]+[^/\\s?#]$", + "type": "string" + }, + "primaryUpstreamProvider": { + "description": "PrimaryUpstreamProvider names the upstream IDP whose access token Cedar\nshould read claims from when authorising a request. Must match the name\nof one of the entries in UpstreamProviders. When empty, the controller\nauto-selects the first entry of UpstreamProviders.\n\nOnly meaningful on VirtualMCPServer, where multiple upstream providers\ncan be configured and Cedar needs to pick which token's claims to\nevaluate. The VirtualMCPServer controller validates this field against\nUpstreamProviders at admission and rejects unresolvable values.\n\nOn MCPServer and MCPRemoteProxy this field is structurally present (the\nEmbeddedAuthServerConfig struct is shared) but has no runtime effect:\nthose CRDs are restricted to a single upstream so there is no choice to\nmake. Setting it on those CRDs is silently ignored.", + "maxLength": 63, + "minLength": 1, + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "type": "string" + }, + "signingKeySecretRefs": { + "description": "SigningKeySecretRefs references Kubernetes Secrets containing signing keys for JWT operations.\nSupports key rotation by allowing multiple keys (oldest keys are used for verification only).\nIf not specified, an ephemeral signing key will be auto-generated (development only -\nJWTs will be invalid after restart).", + "items": { + "description": "SecretKeyRef is a reference to a key within a Secret", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "maxItems": 5, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "storage": { + "description": "Storage configures the storage backend for the embedded auth server.\nIf not specified, defaults to in-memory storage.", + "properties": { + "redis": { + "description": "Redis configures the Redis storage backend.\nRequired when type is \"redis\".", + "properties": { + "aclUserConfig": { + "description": "ACLUserConfig configures Redis ACL user authentication.", + "properties": { + "passwordSecretRef": { + "description": "PasswordSecretRef references a Secret containing the Redis ACL password.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "usernameSecretRef": { + "description": "UsernameSecretRef references a Secret containing the Redis ACL username.\nWhen omitted, connections use legacy password-only AUTH. Omit for managed\nRedis tiers that do not support ACL users (e.g. GCP Memorystore Basic/Standard\nHA, Azure Cache for Redis). Set for services that support ACL users (e.g. AWS\nElastiCache non-cluster with Redis 6+ RBAC).", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "passwordSecretRef" + ], + "type": "object", + "additionalProperties": false + }, + "addr": { + "description": "Addr is the Redis server address (host:port). Required for standalone and cluster modes.\nUse for managed Redis services that expose a single endpoint (GCP Memorystore basic tier,\nAWS ElastiCache without cluster mode, or cluster-mode services when clusterMode is true).\nMutually exclusive with sentinelConfig.", + "type": "string" + }, + "clusterMode": { + "description": "ClusterMode enables the Redis Cluster protocol. Set to true when addr points to a\nRedis Cluster discovery endpoint (e.g., GCP Memorystore Cluster, AWS ElastiCache\ncluster mode enabled). Requires addr to be set.", + "type": "boolean" + }, + "dialTimeout": { + "default": "5s", + "description": "DialTimeout is the timeout for establishing connections.\nFormat: Go duration string (e.g., \"5s\", \"1m\").", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "readTimeout": { + "default": "3s", + "description": "ReadTimeout is the timeout for socket reads.\nFormat: Go duration string (e.g., \"3s\", \"1m\").", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "sentinelConfig": { + "description": "SentinelConfig holds Redis Sentinel configuration.\nUse for self-managed Redis with Sentinel-based HA. Mutually exclusive with addr.", + "properties": { + "db": { + "default": 0, + "description": "DB is the Redis database number.", + "format": "int32", + "type": "integer" + }, + "masterName": { + "description": "MasterName is the name of the Redis master monitored by Sentinel.", + "type": "string" + }, + "sentinelAddrs": { + "description": "SentinelAddrs is a list of Sentinel host:port addresses.\nMutually exclusive with SentinelService.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "sentinelService": { + "description": "SentinelService enables automatic discovery from a Kubernetes Service.\nMutually exclusive with SentinelAddrs.", + "properties": { + "name": { + "description": "Name of the Sentinel Service.", + "type": "string" + }, + "namespace": { + "description": "Namespace of the Sentinel Service (defaults to same namespace).", + "type": "string" + }, + "port": { + "default": 26379, + "description": "Port of the Sentinel service.", + "format": "int32", + "type": "integer" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "masterName" + ], + "type": "object", + "additionalProperties": false + }, + "sentinelTls": { + "description": "SentinelTLS configures TLS for connections to Sentinel instances.\nOnly applies when sentinelConfig is set. Presence of this field enables TLS.", + "properties": { + "caCertSecretRef": { + "description": "CACertSecretRef references a Secret containing a PEM-encoded CA certificate\nfor verifying the server. When not specified, system root CAs are used.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "insecureSkipVerify": { + "description": "InsecureSkipVerify skips TLS certificate verification.\nUse when connecting to services with self-signed certificates.", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "tls": { + "description": "TLS configures TLS for connections to the Redis/Valkey master.\nPresence of this field enables TLS. Omit to use plaintext.", + "properties": { + "caCertSecretRef": { + "description": "CACertSecretRef references a Secret containing a PEM-encoded CA certificate\nfor verifying the server. When not specified, system root CAs are used.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "insecureSkipVerify": { + "description": "InsecureSkipVerify skips TLS certificate verification.\nUse when connecting to services with self-signed certificates.", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "writeTimeout": { + "default": "3s", + "description": "WriteTimeout is the timeout for socket writes.\nFormat: Go duration string (e.g., \"3s\", \"1m\").", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + } + }, + "required": [ + "aclUserConfig" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "exactly one of addr or sentinelConfig must be set", + "rule": "(has(self.addr) && self.addr.size() > 0) != has(self.sentinelConfig)" + }, + { + "message": "clusterMode requires addr to be set", + "rule": "!(has(self.clusterMode) && self.clusterMode) || (has(self.addr) && self.addr.size() > 0)" + } + ], + "additionalProperties": false + }, + "type": { + "default": "memory", + "description": "Type specifies the storage backend type.\nValid values: \"memory\" (default), \"redis\".", + "enum": [ + "memory", + "redis" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "tokenLifespans": { + "description": "TokenLifespans configures the duration that various tokens are valid.\nIf not specified, defaults are applied (access: 1h, refresh: 7d, authCode: 10m).", + "properties": { + "accessTokenLifespan": { + "description": "AccessTokenLifespan is the duration that access tokens are valid.\nFormat: Go duration string (e.g., \"1h\", \"30m\", \"24h\").\nIf empty, defaults to 1 hour.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "authCodeLifespan": { + "description": "AuthCodeLifespan is the duration that authorization codes are valid.\nFormat: Go duration string (e.g., \"10m\", \"5m\").\nIf empty, defaults to 10 minutes.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "refreshTokenLifespan": { + "description": "RefreshTokenLifespan is the duration that refresh tokens are valid.\nFormat: Go duration string (e.g., \"168h\", \"7d\" as \"168h\").\nIf empty, defaults to 7 days (168h).", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "upstreamProviders": { + "description": "UpstreamProviders configures connections to upstream Identity Providers.\nThe embedded auth server delegates authentication to these providers.\nMCPServer and MCPRemoteProxy support a single upstream; VirtualMCPServer supports multiple.", + "items": { + "description": "UpstreamProviderConfig defines configuration for an upstream Identity Provider.\n\nExactly one of OIDCConfig or OAuth2Config must be set and must match the\ndeclared Type: oidc-typed providers set OIDCConfig, oauth2-typed providers\nset OAuth2Config. The CEL rule below enforces the pairing at admission; the\nmatching Go-level check in validateUpstreamProvider provides defense-in-depth\nfor stored objects.\n\nThe rule is structured as a chain of equality checks ending in an explicit\n`false`, so adding a new UpstreamProviderType value without extending this\nrule fails admission instead of silently demanding the OAuth2 shape. When\nadding a new type, extend both this rule and validateUpstreamProvider.", + "properties": { + "name": { + "description": "Name uniquely identifies this upstream provider.\nUsed for routing decisions and session binding in multi-upstream scenarios.\nMust be lowercase alphanumeric with hyphens (DNS-label-like).", + "maxLength": 63, + "minLength": 1, + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "type": "string" + }, + "oauth2Config": { + "description": "OAuth2Config contains OAuth 2.0-specific configuration.\nRequired when Type is \"oauth2\", must be nil when Type is \"oidc\".", + "properties": { + "additionalAuthorizationParams": { + "additionalProperties": { + "type": "string" + }, + "description": "AdditionalAuthorizationParams are extra query parameters to include in\nauthorization requests sent to the upstream provider.\nThis is useful for providers that require custom parameters, such as\nGoogle's access_type=offline for obtaining refresh tokens.\nFramework-managed parameters (response_type, client_id, redirect_uri,\nscope, state, code_challenge, code_challenge_method, nonce) are not allowed.", + "maxProperties": 16, + "type": "object" + }, + "authorizationEndpoint": { + "description": "AuthorizationEndpoint is the URL for the OAuth authorization endpoint.", + "pattern": "^https?://.*$", + "type": "string" + }, + "clientId": { + "description": "ClientID is the OAuth 2.0 client identifier registered with the upstream IDP.\nMutually exclusive with DCRConfig: when DCRConfig is set, ClientID is obtained\nat runtime via RFC 7591 Dynamic Client Registration and must be left empty.", + "type": "string" + }, + "clientSecretRef": { + "description": "ClientSecretRef references a Kubernetes Secret containing the OAuth 2.0 client secret.\nOptional for public clients using PKCE instead of client secret.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "dcrConfig": { + "description": "DCRConfig enables RFC 7591 Dynamic Client Registration against the upstream\nauthorization server. When set, the client credentials are obtained at\nruntime rather than being pre-provisioned, and ClientID must be left empty.\nMutually exclusive with ClientID.", + "properties": { + "discoveryUrl": { + "description": "DiscoveryURL is the RFC 8414 / OIDC Discovery document URL. The resolver\nissues a single GET against this URL (no well-known-path fallback) and\nreads registration_endpoint, authorization_endpoint, token_endpoint,\ntoken_endpoint_auth_methods_supported, and scopes_supported from the\nresponse.\nMutually exclusive with RegistrationEndpoint.\nHTTPS is required because the registration endpoint resolved from this\ndocument carries the initial access token and the issued client_secret\n(RFC 7591 \u00a73, RFC 8414 \u00a73). MaxLength is a defensive size cap (etcd\nobject budget, regex evaluation cost) and matches the conventional URL\nlength cap.", + "maxLength": 2048, + "pattern": "^https://[^\\s?#]+[^/\\s?#]$", + "type": "string" + }, + "initialAccessTokenRef": { + "description": "InitialAccessTokenRef is an optional reference to a Kubernetes Secret\ncarrying an RFC 7591 \u00a73 initial access token. When set, the resolver\npresents the token value as a Bearer credential on the registration\nrequest. Mirrors the ClientSecretRef pattern.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "registrationEndpoint": { + "description": "RegistrationEndpoint is the RFC 7591 registration endpoint URL used\ndirectly, bypassing discovery. When using this field, the caller is\nexpected to also supply AuthorizationEndpoint, TokenEndpoint, and an\nexplicit Scopes list on the parent OAuth2UpstreamConfig.\nMutually exclusive with DiscoveryURL.\nHTTPS is required because the registration endpoint carries the initial\naccess token and the issued client_secret (RFC 7591 \u00a73, RFC 8414 \u00a73).\nMaxLength is a defensive size cap (etcd object budget, regex evaluation\ncost) and matches the conventional URL length cap.", + "maxLength": 2048, + "pattern": "^https://[^\\s?#]+[^/\\s?#]$", + "type": "string" + }, + "softwareId": { + "description": "SoftwareID is the RFC 7591 \"software_id\" registration metadata value,\nidentifying the client software independent of any particular\nregistration instance. Typically a UUID or short identifier.", + "maxLength": 255, + "type": "string" + }, + "softwareStatement": { + "description": "SoftwareStatement is the RFC 7591 \"software_statement\" JWT asserting\nmetadata about the client software, signed by a party the authorization\nserver trusts.\n\nStored inline on the CR. The JWT is signed but not encrypted, so its\ncontents are visible to anyone with get/list/watch on this resource and\nappear in etcd backups in plaintext. Treat the value as non-confidential\n(signed attestation, not a secret). Operators that rotate software\nstatements like bearer credentials should keep them at the authorization\nserver side and rely on the registration endpoint's initial access\ntoken (see InitialAccessTokenRef) instead of placing them on the CR.\n\nBounded to 16384 characters as a defensive size cap (etcd object\nbudget, regex evaluation cost). Real-world signed statements with\nembedded x5c certificate chains, JWKS keys, or OIDC-Federation\ntrust-framework metadata routinely exceed 4 KB.", + "maxLength": 16384, + "type": "string" + } + }, + "type": "object", + "x-kubernetes-validations": [ + { + "message": "exactly one of discoveryUrl or registrationEndpoint must be set", + "rule": "has(self.discoveryUrl) != has(self.registrationEndpoint)" + } + ], + "additionalProperties": false + }, + "identityFromToken": { + "description": "IdentityFromToken extracts user identity (subject, name, email) directly\nfrom the OAuth2 token-endpoint response body using gjson dot-notation paths.\nWhen set, the embedded auth server skips the userinfo HTTP call entirely\nand resolves identity from the token response. See IdentityFromTokenConfig\nfor trust-model and uniqueness considerations.", + "properties": { + "emailPath": { + "description": "EmailPath is the dot-notation path to the email address field in the token response.\nIf not specified or if the path does not resolve to a string, the email is omitted.\nOmit the field entirely rather than setting it to an empty string.", + "maxLength": 256, + "minLength": 1, + "type": "string" + }, + "namePath": { + "description": "NamePath is the dot-notation path to the display name field in the token response.\nIf not specified or if the path does not resolve to a string, the display name is omitted.\nOmit the field entirely rather than setting it to an empty string.", + "maxLength": 256, + "minLength": 1, + "type": "string" + }, + "subjectPath": { + "description": "SubjectPath is the dot-notation path to the subject (user ID) field in the token response.\nWarning: claims read from the token response are trusted only via TLS, not\ncryptographically verified; prefer OIDC ID tokens when verifiable claims are required.\nExample: \"authed_user.id\" for Slack (top-level token-response field). For providers\nwhose token response embeds the access token as a JWT (e.g. Snowflake), use the\n\"@upstreamjwt\" modifier to decode the payload, e.g. \"access_token|@upstreamjwt|sub\".\nThe \"@upstreamjwt\" modifier performs no signature verification either.", + "maxLength": 256, + "minLength": 1, + "type": "string" + } + }, + "required": [ + "subjectPath" + ], + "type": "object", + "additionalProperties": false + }, + "redirectUri": { + "description": "RedirectURI is the callback URL where the upstream IDP will redirect after authentication.\nWhen not specified, defaults to `{resourceUrl}/oauth/callback` where `resourceUrl` is the\nURL associated with the resource (e.g., MCPServer or vMCP) using this config.", + "type": "string" + }, + "scopes": { + "description": "Scopes are the OAuth scopes to request from the upstream IDP.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "tokenEndpoint": { + "description": "TokenEndpoint is the URL for the OAuth token endpoint.", + "pattern": "^https?://.*$", + "type": "string" + }, + "tokenResponseMapping": { + "description": "TokenResponseMapping configures custom field extraction from non-standard token responses.\nSome OAuth providers (e.g., GovSlack) nest token fields under non-standard paths\ninstead of returning them at the top level. When set, ToolHive performs the token\nexchange HTTP call directly and extracts fields using the configured dot-notation paths.\nIf nil, standard OAuth 2.0 token response parsing is used.\nFor extracting user identity from the token response, see IdentityFromToken.", + "properties": { + "accessTokenPath": { + "description": "AccessTokenPath is the dot-notation path to the access token in the response.\nExample: \"authed_user.access_token\"", + "minLength": 1, + "type": "string" + }, + "expiresInPath": { + "description": "ExpiresInPath is the dot-notation path to the expires_in value (in seconds).\nIf not specified, defaults to \"expires_in\".", + "type": "string" + }, + "refreshTokenPath": { + "description": "RefreshTokenPath is the dot-notation path to the refresh token in the response.\nIf not specified, defaults to \"refresh_token\".", + "type": "string" + }, + "scopePath": { + "description": "ScopePath is the dot-notation path to the scope string in the response.\nIf not specified, defaults to \"scope\".", + "type": "string" + } + }, + "required": [ + "accessTokenPath" + ], + "type": "object", + "additionalProperties": false + }, + "userInfo": { + "description": "UserInfo contains configuration for fetching user information from the upstream provider.\nWhen omitted and IdentityFromToken is also unset, the embedded auth server runs in\nsynthesis mode for this upstream: a non-PII subject derived from the access token, no\nName/Email. Use this shape for upstreams with no userinfo surface and no identity in\nthe token response (e.g., MCP authorization servers per the MCP spec). When\nIdentityFromToken is set instead, identity is resolved from the token response body\n(e.g., Snowflake's \"username\" field, Slack's \"authed_user.id\"); the userinfo HTTP call\nis skipped entirely.", + "properties": { + "additionalHeaders": { + "additionalProperties": { + "type": "string" + }, + "description": "AdditionalHeaders contains extra headers to include in the userinfo request.\nUseful for providers that require specific headers (e.g., GitHub's Accept header).", + "type": "object" + }, + "endpointUrl": { + "description": "EndpointURL is the URL of the userinfo endpoint.", + "pattern": "^https?://.*$", + "type": "string" + }, + "fieldMapping": { + "description": "FieldMapping contains custom field mapping configuration for non-standard providers.\nIf nil, standard OIDC field names are used (\"sub\", \"name\", \"email\").", + "properties": { + "emailFields": { + "description": "EmailFields is an ordered list of field names to try for the email address.\nThe first non-empty value found will be used.\nDefault: [\"email\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "nameFields": { + "description": "NameFields is an ordered list of field names to try for the display name.\nThe first non-empty value found will be used.\nDefault: [\"name\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "subjectFields": { + "description": "SubjectFields is an ordered list of field names to try for the user ID.\nThe first non-empty value found will be used.\nDefault: [\"sub\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "type": "object", + "additionalProperties": false + }, + "httpMethod": { + "description": "HTTPMethod is the HTTP method to use for the userinfo request.\nIf not specified, defaults to GET.", + "enum": [ + "GET", + "POST" + ], + "type": "string" + } + }, + "required": [ + "endpointUrl" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "authorizationEndpoint", + "tokenEndpoint" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "exactly one of clientId or dcrConfig must be set", + "rule": "(has(self.clientId) && size(self.clientId) > 0) ? !has(self.dcrConfig) : has(self.dcrConfig)" + }, + { + "message": "clientSecretRef must not be set when dcrConfig is set; the client_secret is obtained at runtime via Dynamic Client Registration", + "rule": "!(has(self.dcrConfig) && has(self.clientSecretRef))" + } + ], + "additionalProperties": false + }, + "oidcConfig": { + "description": "OIDCConfig contains OIDC-specific configuration.\nRequired when Type is \"oidc\", must be nil when Type is \"oauth2\".", + "properties": { + "additionalAuthorizationParams": { + "additionalProperties": { + "type": "string" + }, + "description": "AdditionalAuthorizationParams are extra query parameters to include in\nauthorization requests sent to the upstream provider.\nThis is useful for providers that require custom parameters, such as\nGoogle's access_type=offline for obtaining refresh tokens.\nNote: when using access_type=offline, also set explicit scopes to avoid\nthe default offline_access scope being sent alongside it.\nFramework-managed parameters (response_type, client_id, redirect_uri,\nscope, state, code_challenge, code_challenge_method, nonce) are not allowed.", + "maxProperties": 16, + "type": "object" + }, + "clientId": { + "description": "ClientID is the OAuth 2.0 client identifier registered with the upstream IDP.", + "type": "string" + }, + "clientSecretRef": { + "description": "ClientSecretRef references a Kubernetes Secret containing the OAuth 2.0 client secret.\nOptional for public clients using PKCE instead of client secret.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "issuerUrl": { + "description": "IssuerURL is the OIDC issuer URL for automatic endpoint discovery.\nMust be a valid HTTPS URL.", + "pattern": "^https://.*$", + "type": "string" + }, + "redirectUri": { + "description": "RedirectURI is the callback URL where the upstream IDP will redirect after authentication.\nWhen not specified, defaults to `{resourceUrl}/oauth/callback` where `resourceUrl` is the\nURL associated with the resource (e.g., MCPServer or vMCP) using this config.", + "type": "string" + }, + "scopes": { + "description": "Scopes are the OAuth scopes to request from the upstream IDP.\nIf not specified, defaults to [\"openid\", \"offline_access\"].\nWhen using additionalAuthorizationParams with provider-specific refresh token\nmechanisms (e.g., Google's access_type=offline), set explicit scopes to avoid\nsending both offline_access and the provider-specific parameter.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "userInfoOverride": { + "description": "UserInfoOverride allows customizing UserInfo fetching behavior for OIDC providers.\nBy default, the UserInfo endpoint is discovered automatically via OIDC discovery.\nUse this to override the endpoint URL, HTTP method, or field mappings for providers\nthat return non-standard claim names in their UserInfo response.", + "properties": { + "additionalHeaders": { + "additionalProperties": { + "type": "string" + }, + "description": "AdditionalHeaders contains extra headers to include in the userinfo request.\nUseful for providers that require specific headers (e.g., GitHub's Accept header).", + "type": "object" + }, + "endpointUrl": { + "description": "EndpointURL is the URL of the userinfo endpoint.", + "pattern": "^https?://.*$", + "type": "string" + }, + "fieldMapping": { + "description": "FieldMapping contains custom field mapping configuration for non-standard providers.\nIf nil, standard OIDC field names are used (\"sub\", \"name\", \"email\").", + "properties": { + "emailFields": { + "description": "EmailFields is an ordered list of field names to try for the email address.\nThe first non-empty value found will be used.\nDefault: [\"email\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "nameFields": { + "description": "NameFields is an ordered list of field names to try for the display name.\nThe first non-empty value found will be used.\nDefault: [\"name\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "subjectFields": { + "description": "SubjectFields is an ordered list of field names to try for the user ID.\nThe first non-empty value found will be used.\nDefault: [\"sub\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "type": "object", + "additionalProperties": false + }, + "httpMethod": { + "description": "HTTPMethod is the HTTP method to use for the userinfo request.\nIf not specified, defaults to GET.", + "enum": [ + "GET", + "POST" + ], + "type": "string" + } + }, + "required": [ + "endpointUrl" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "clientId", + "issuerUrl" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type specifies the provider type: \"oidc\" or \"oauth2\"", + "enum": [ + "oidc", + "oauth2" + ], + "type": "string" + } + }, + "required": [ + "name", + "type" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "type must be 'oidc' or 'oauth2'; oidcConfig must be set when type is 'oidc' and oauth2Config must be set when type is 'oauth2' (and the other must not be set)", + "rule": "self.type == 'oidc' ? (has(self.oidcConfig) && !has(self.oauth2Config)) : self.type == 'oauth2' ? (has(self.oauth2Config) && !has(self.oidcConfig)) : false" + } + ], + "additionalProperties": false + }, + "minItems": 1, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "required": [ + "issuer", + "upstreamProviders" + ], + "type": "object", + "additionalProperties": false + }, + "headerInjection": { + "description": "HeaderInjection configures custom HTTP header injection\nOnly used when Type is \"headerInjection\"", + "properties": { + "headerName": { + "description": "HeaderName is the name of the HTTP header to inject", + "minLength": 1, + "type": "string" + }, + "valueSecretRef": { + "description": "ValueSecretRef references a Kubernetes Secret containing the header value", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "headerName", + "valueSecretRef" + ], + "type": "object", + "additionalProperties": false + }, + "tokenExchange": { + "description": "TokenExchange configures RFC-8693 OAuth 2.0 Token Exchange\nOnly used when Type is \"tokenExchange\"", + "properties": { + "audience": { + "description": "Audience is the target audience for the exchanged token", + "type": "string" + }, + "clientId": { + "description": "ClientID is the OAuth 2.0 client identifier\nOptional for some token exchange flows (e.g., Google Cloud Workforce Identity)", + "type": "string" + }, + "clientSecretRef": { + "description": "ClientSecretRef is a reference to a secret containing the OAuth 2.0 client secret\nOptional for some token exchange flows (e.g., Google Cloud Workforce Identity)", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "externalTokenHeaderName": { + "description": "ExternalTokenHeaderName is the name of the custom header to use for the exchanged token.\nIf set, the exchanged token will be added to this custom header (e.g., \"X-Upstream-Token\").\nIf empty or not set, the exchanged token will replace the Authorization header (default behavior).", + "type": "string" + }, + "scopes": { + "description": "Scopes is a list of OAuth 2.0 scopes to request for the exchanged token", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "subjectProviderName": { + "description": "SubjectProviderName is the name of the upstream provider whose token is used as the\nRFC 8693 subject token instead of identity.Token when performing token exchange.\nWhen left empty and an embedded authorization server is configured on the VirtualMCPServer,\nthe controller automatically populates this field with the first configured upstream\nprovider name. Set it explicitly to override that default or to select a specific\nprovider when multiple upstreams are configured.", + "type": "string" + }, + "subjectTokenType": { + "description": "SubjectTokenType is the type of the incoming subject token.\nAccepts short forms: \"access_token\" (default), \"id_token\", \"jwt\"\nOr full URNs: \"urn:ietf:params:oauth:token-type:access_token\",\n \"urn:ietf:params:oauth:token-type:id_token\",\n \"urn:ietf:params:oauth:token-type:jwt\"\nFor Google Workload Identity Federation with OIDC providers (like Okta), use \"id_token\"", + "pattern": "^(access_token|id_token|jwt|urn:ietf:params:oauth:token-type:(access_token|id_token|jwt))?$", + "type": "string" + }, + "tokenUrl": { + "description": "TokenURL is the OAuth 2.0 token endpoint URL for token exchange", + "type": "string" + } + }, + "required": [ + "audience", + "tokenUrl" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type is the type of external authentication to configure", + "enum": [ + "tokenExchange", + "headerInjection", + "bearerToken", + "unauthenticated", + "embeddedAuthServer", + "awsSts", + "upstreamInject" + ], + "type": "string" + }, + "upstreamInject": { + "description": "UpstreamInject configures upstream token injection for backend requests.\nOnly used when Type is \"upstreamInject\".", + "properties": { + "providerName": { + "description": "ProviderName is the name of the upstream IDP provider whose access token\nshould be injected as the Authorization: Bearer header.", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "providerName" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "type" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "tokenExchange configuration must be set if and only if type is 'tokenExchange'", + "rule": "self.type == 'tokenExchange' ? has(self.tokenExchange) : !has(self.tokenExchange)" + }, + { + "message": "headerInjection configuration must be set if and only if type is 'headerInjection'", + "rule": "self.type == 'headerInjection' ? has(self.headerInjection) : !has(self.headerInjection)" + }, + { + "message": "bearerToken configuration must be set if and only if type is 'bearerToken'", + "rule": "self.type == 'bearerToken' ? has(self.bearerToken) : !has(self.bearerToken)" + }, + { + "message": "embeddedAuthServer configuration must be set if and only if type is 'embeddedAuthServer'", + "rule": "self.type == 'embeddedAuthServer' ? has(self.embeddedAuthServer) : !has(self.embeddedAuthServer)" + }, + { + "message": "awsSts configuration must be set if and only if type is 'awsSts'", + "rule": "self.type == 'awsSts' ? has(self.awsSts) : !has(self.awsSts)" + }, + { + "message": "upstreamInject configuration must be set if and only if type is 'upstreamInject'", + "rule": "self.type == 'upstreamInject' ? has(self.upstreamInject) : !has(self.upstreamInject)" + }, + { + "message": "no configuration must be set when type is 'unauthenticated'", + "rule": "self.type == 'unauthenticated' ? (!has(self.tokenExchange) && !has(self.headerInjection) && !has(self.bearerToken) && !has(self.embeddedAuthServer) && !has(self.awsSts) && !has(self.upstreamInject)) : true" + } + ], + "additionalProperties": false + }, + "status": { + "description": "MCPExternalAuthConfigStatus defines the observed state of MCPExternalAuthConfig", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations of the MCPExternalAuthConfig's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "configHash": { + "description": "ConfigHash is a hash of the current configuration for change detection", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration is the most recent generation observed for this MCPExternalAuthConfig.\nIt corresponds to the MCPExternalAuthConfig's generation, which is updated on mutation by the API Server.", + "format": "int64", + "type": "integer" + }, + "referenceCount": { + "description": "ReferenceCount is the number of workloads referencing this config.", + "format": "int32", + "type": "integer" + }, + "referencingWorkloads": { + "description": "ReferencingWorkloads is a list of workload resources that reference this MCPExternalAuthConfig.\nEach entry identifies the workload by kind and name.", + "items": { + "description": "WorkloadReference identifies a workload that references a shared configuration resource.\nNamespace is implicit \u2014 cross-namespace references are not supported.", + "properties": { + "kind": { + "description": "Kind is the type of workload resource", + "enum": [ + "MCPServer", + "VirtualMCPServer", + "MCPRemoteProxy" + ], + "type": "string" + }, + "name": { + "description": "Name is the name of the workload resource", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/mcpgroup_v1alpha1.json b/crdSchemas/mcpgroup_v1alpha1.json new file mode 100644 index 0000000..83ab2e4 --- /dev/null +++ b/crdSchemas/mcpgroup_v1alpha1.json @@ -0,0 +1,149 @@ +{ + "description": "MCPGroup is the deprecated v1alpha1 version of the MCPGroup resource.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPGroupSpec defines the desired state of MCPGroup", + "properties": { + "description": { + "description": "Description provides human-readable context", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "status": { + "description": "MCPGroupStatus defines observed state", + "properties": { + "conditions": { + "description": "Conditions represent observations", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "entries": { + "description": "Entries lists MCPServerEntry names in this group", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "set" + }, + "entryCount": { + "description": "EntryCount is the number of MCPServerEntries", + "format": "int32", + "type": "integer" + }, + "observedGeneration": { + "description": "ObservedGeneration reflects the generation most recently observed by the controller", + "format": "int64", + "type": "integer" + }, + "phase": { + "default": "Pending", + "description": "Phase indicates current state", + "enum": [ + "Ready", + "Pending", + "Failed" + ], + "type": "string" + }, + "remoteProxies": { + "description": "RemoteProxies lists MCPRemoteProxy names in this group", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "set" + }, + "remoteProxyCount": { + "description": "RemoteProxyCount is the number of MCPRemoteProxies", + "format": "int32", + "type": "integer" + }, + "serverCount": { + "description": "ServerCount is the number of MCPServers", + "format": "int32", + "type": "integer" + }, + "servers": { + "description": "Servers lists MCPServer names in this group", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "set" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/mcpgroup_v1beta1.json b/crdSchemas/mcpgroup_v1beta1.json new file mode 100644 index 0000000..dfca32c --- /dev/null +++ b/crdSchemas/mcpgroup_v1beta1.json @@ -0,0 +1,149 @@ +{ + "description": "MCPGroup is the Schema for the mcpgroups API", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPGroupSpec defines the desired state of MCPGroup", + "properties": { + "description": { + "description": "Description provides human-readable context", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "status": { + "description": "MCPGroupStatus defines observed state", + "properties": { + "conditions": { + "description": "Conditions represent observations", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "entries": { + "description": "Entries lists MCPServerEntry names in this group", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "set" + }, + "entryCount": { + "description": "EntryCount is the number of MCPServerEntries", + "format": "int32", + "type": "integer" + }, + "observedGeneration": { + "description": "ObservedGeneration reflects the generation most recently observed by the controller", + "format": "int64", + "type": "integer" + }, + "phase": { + "default": "Pending", + "description": "Phase indicates current state", + "enum": [ + "Ready", + "Pending", + "Failed" + ], + "type": "string" + }, + "remoteProxies": { + "description": "RemoteProxies lists MCPRemoteProxy names in this group", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "set" + }, + "remoteProxyCount": { + "description": "RemoteProxyCount is the number of MCPRemoteProxies", + "format": "int32", + "type": "integer" + }, + "serverCount": { + "description": "ServerCount is the number of MCPServers", + "format": "int32", + "type": "integer" + }, + "servers": { + "description": "Servers lists MCPServer names in this group", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "set" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/mcpoidcconfig_v1alpha1.json b/crdSchemas/mcpoidcconfig_v1alpha1.json new file mode 100644 index 0000000..aed96c8 --- /dev/null +++ b/crdSchemas/mcpoidcconfig_v1alpha1.json @@ -0,0 +1,286 @@ +{ + "description": "MCPOIDCConfig is the deprecated v1alpha1 version of the MCPOIDCConfig resource.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPOIDCConfigSpec defines the desired state of MCPOIDCConfig.\nMCPOIDCConfig resources are namespace-scoped and can only be referenced by\nMCPServer resources in the same namespace.", + "properties": { + "inline": { + "description": "Inline contains direct OIDC configuration.\nOnly used when Type is \"inline\".", + "properties": { + "caBundleRef": { + "description": "CABundleRef references a ConfigMap containing the CA certificate bundle.\nWhen specified, ToolHive auto-mounts the ConfigMap and auto-computes ThvCABundlePath.", + "properties": { + "configMapRef": { + "description": "ConfigMapRef references a ConfigMap containing the CA certificate bundle.\nIf Key is not specified, it defaults to \"ca.crt\".", + "properties": { + "key": { + "description": "The key to select.", + "type": "string" + }, + "name": { + "default": "", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + }, + "optional": { + "description": "Specify whether the ConfigMap or its key must be defined", + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "clientId": { + "description": "ClientID is the OIDC client ID", + "type": "string" + }, + "clientSecretRef": { + "description": "ClientSecretRef is a reference to a Kubernetes Secret containing the client secret", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "insecureAllowHTTP": { + "default": false, + "description": "InsecureAllowHTTP allows HTTP (non-HTTPS) OIDC issuers for development/testing.\nWARNING: This is insecure and should NEVER be used in production.", + "type": "boolean" + }, + "introspectionUrl": { + "description": "IntrospectionURL is the URL for token introspection endpoint", + "type": "string" + }, + "issuer": { + "description": "Issuer is the OIDC issuer URL", + "type": "string" + }, + "jwksAllowPrivateIP": { + "default": false, + "description": "JWKSAllowPrivateIP allows JWKS/OIDC endpoints on private IP addresses.\nNote: at runtime, if either JWKSAllowPrivateIP or ProtectedResourceAllowPrivateIP\nis true, private IPs are allowed for all OIDC HTTP requests (JWKS, discovery, introspection).", + "type": "boolean" + }, + "jwksAuthTokenPath": { + "description": "JWKSAuthTokenPath is the path to file containing bearer token for JWKS/OIDC requests", + "type": "string" + }, + "jwksUrl": { + "description": "JWKSURL is the URL to fetch the JWKS from", + "type": "string" + }, + "protectedResourceAllowPrivateIP": { + "default": false, + "description": "ProtectedResourceAllowPrivateIP allows protected resource endpoint on private IP addresses.\nNote: at runtime, if either ProtectedResourceAllowPrivateIP or JWKSAllowPrivateIP\nis true, private IPs are allowed for all OIDC HTTP requests (JWKS, discovery, introspection).", + "type": "boolean" + } + }, + "required": [ + "issuer" + ], + "type": "object", + "additionalProperties": false + }, + "kubernetesServiceAccount": { + "description": "KubernetesServiceAccount configures OIDC for Kubernetes service account token validation.\nOnly used when Type is \"kubernetesServiceAccount\".", + "properties": { + "introspectionUrl": { + "description": "IntrospectionURL is the URL for token introspection endpoint.\nIf empty, OIDC discovery will be used to automatically determine the introspection URL.", + "type": "string" + }, + "issuer": { + "default": "https://kubernetes.default.svc", + "description": "Issuer is the OIDC issuer URL.", + "type": "string" + }, + "jwksUrl": { + "description": "JWKSURL is the URL to fetch the JWKS from.\nIf empty, OIDC discovery will be used to automatically determine the JWKS URL.", + "type": "string" + }, + "namespace": { + "description": "Namespace is the namespace of the service account.\nIf empty, uses the MCPServer's namespace.", + "type": "string" + }, + "serviceAccount": { + "description": "ServiceAccount is the name of the service account to validate tokens for.\nIf empty, uses the pod's service account.", + "type": "string" + }, + "useClusterAuth": { + "description": "UseClusterAuth enables using the Kubernetes cluster's CA bundle and service account token.\nWhen true, uses /var/run/secrets/kubernetes.io/serviceaccount/ca.crt for TLS verification\nand /var/run/secrets/kubernetes.io/serviceaccount/token for bearer token authentication.\nDefaults to true if not specified.", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type is the type of OIDC configuration source", + "enum": [ + "kubernetesServiceAccount", + "inline" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "kubernetesServiceAccount must be set when type is 'kubernetesServiceAccount', and must not be set otherwise", + "rule": "self.type == 'kubernetesServiceAccount' ? has(self.kubernetesServiceAccount) : !has(self.kubernetesServiceAccount)" + }, + { + "message": "inline must be set when type is 'inline', and must not be set otherwise", + "rule": "self.type == 'inline' ? has(self.inline) : !has(self.inline)" + } + ], + "additionalProperties": false + }, + "status": { + "description": "MCPOIDCConfigStatus defines the observed state of MCPOIDCConfig", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations of the MCPOIDCConfig's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "configHash": { + "description": "ConfigHash is a hash of the current configuration for change detection", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration is the most recent generation observed for this MCPOIDCConfig.", + "format": "int64", + "type": "integer" + }, + "referenceCount": { + "description": "ReferenceCount is the number of workloads referencing this config.", + "format": "int32", + "type": "integer" + }, + "referencingWorkloads": { + "description": "ReferencingWorkloads is a list of workload resources that reference this MCPOIDCConfig.\nEach entry identifies the workload by kind and name.", + "items": { + "description": "WorkloadReference identifies a workload that references a shared configuration resource.\nNamespace is implicit \u2014 cross-namespace references are not supported.", + "properties": { + "kind": { + "description": "Kind is the type of workload resource", + "enum": [ + "MCPServer", + "VirtualMCPServer", + "MCPRemoteProxy" + ], + "type": "string" + }, + "name": { + "description": "Name is the name of the workload resource", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/mcpoidcconfig_v1beta1.json b/crdSchemas/mcpoidcconfig_v1beta1.json new file mode 100644 index 0000000..7ecc507 --- /dev/null +++ b/crdSchemas/mcpoidcconfig_v1beta1.json @@ -0,0 +1,286 @@ +{ + "description": "MCPOIDCConfig is the Schema for the mcpoidcconfigs API.\nMCPOIDCConfig resources are namespace-scoped and can only be referenced by\nMCPServer resources within the same namespace. Cross-namespace references\nare not supported for security and isolation reasons.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPOIDCConfigSpec defines the desired state of MCPOIDCConfig.\nMCPOIDCConfig resources are namespace-scoped and can only be referenced by\nMCPServer resources in the same namespace.", + "properties": { + "inline": { + "description": "Inline contains direct OIDC configuration.\nOnly used when Type is \"inline\".", + "properties": { + "caBundleRef": { + "description": "CABundleRef references a ConfigMap containing the CA certificate bundle.\nWhen specified, ToolHive auto-mounts the ConfigMap and auto-computes ThvCABundlePath.", + "properties": { + "configMapRef": { + "description": "ConfigMapRef references a ConfigMap containing the CA certificate bundle.\nIf Key is not specified, it defaults to \"ca.crt\".", + "properties": { + "key": { + "description": "The key to select.", + "type": "string" + }, + "name": { + "default": "", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + }, + "optional": { + "description": "Specify whether the ConfigMap or its key must be defined", + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "clientId": { + "description": "ClientID is the OIDC client ID", + "type": "string" + }, + "clientSecretRef": { + "description": "ClientSecretRef is a reference to a Kubernetes Secret containing the client secret", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "insecureAllowHTTP": { + "default": false, + "description": "InsecureAllowHTTP allows HTTP (non-HTTPS) OIDC issuers for development/testing.\nWARNING: This is insecure and should NEVER be used in production.", + "type": "boolean" + }, + "introspectionUrl": { + "description": "IntrospectionURL is the URL for token introspection endpoint", + "type": "string" + }, + "issuer": { + "description": "Issuer is the OIDC issuer URL", + "type": "string" + }, + "jwksAllowPrivateIP": { + "default": false, + "description": "JWKSAllowPrivateIP allows JWKS/OIDC endpoints on private IP addresses.\nNote: at runtime, if either JWKSAllowPrivateIP or ProtectedResourceAllowPrivateIP\nis true, private IPs are allowed for all OIDC HTTP requests (JWKS, discovery, introspection).", + "type": "boolean" + }, + "jwksAuthTokenPath": { + "description": "JWKSAuthTokenPath is the path to file containing bearer token for JWKS/OIDC requests", + "type": "string" + }, + "jwksUrl": { + "description": "JWKSURL is the URL to fetch the JWKS from", + "type": "string" + }, + "protectedResourceAllowPrivateIP": { + "default": false, + "description": "ProtectedResourceAllowPrivateIP allows protected resource endpoint on private IP addresses.\nNote: at runtime, if either ProtectedResourceAllowPrivateIP or JWKSAllowPrivateIP\nis true, private IPs are allowed for all OIDC HTTP requests (JWKS, discovery, introspection).", + "type": "boolean" + } + }, + "required": [ + "issuer" + ], + "type": "object", + "additionalProperties": false + }, + "kubernetesServiceAccount": { + "description": "KubernetesServiceAccount configures OIDC for Kubernetes service account token validation.\nOnly used when Type is \"kubernetesServiceAccount\".", + "properties": { + "introspectionUrl": { + "description": "IntrospectionURL is the URL for token introspection endpoint.\nIf empty, OIDC discovery will be used to automatically determine the introspection URL.", + "type": "string" + }, + "issuer": { + "default": "https://kubernetes.default.svc", + "description": "Issuer is the OIDC issuer URL.", + "type": "string" + }, + "jwksUrl": { + "description": "JWKSURL is the URL to fetch the JWKS from.\nIf empty, OIDC discovery will be used to automatically determine the JWKS URL.", + "type": "string" + }, + "namespace": { + "description": "Namespace is the namespace of the service account.\nIf empty, uses the MCPServer's namespace.", + "type": "string" + }, + "serviceAccount": { + "description": "ServiceAccount is the name of the service account to validate tokens for.\nIf empty, uses the pod's service account.", + "type": "string" + }, + "useClusterAuth": { + "description": "UseClusterAuth enables using the Kubernetes cluster's CA bundle and service account token.\nWhen true, uses /var/run/secrets/kubernetes.io/serviceaccount/ca.crt for TLS verification\nand /var/run/secrets/kubernetes.io/serviceaccount/token for bearer token authentication.\nDefaults to true if not specified.", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type is the type of OIDC configuration source", + "enum": [ + "kubernetesServiceAccount", + "inline" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "kubernetesServiceAccount must be set when type is 'kubernetesServiceAccount', and must not be set otherwise", + "rule": "self.type == 'kubernetesServiceAccount' ? has(self.kubernetesServiceAccount) : !has(self.kubernetesServiceAccount)" + }, + { + "message": "inline must be set when type is 'inline', and must not be set otherwise", + "rule": "self.type == 'inline' ? has(self.inline) : !has(self.inline)" + } + ], + "additionalProperties": false + }, + "status": { + "description": "MCPOIDCConfigStatus defines the observed state of MCPOIDCConfig", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations of the MCPOIDCConfig's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "configHash": { + "description": "ConfigHash is a hash of the current configuration for change detection", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration is the most recent generation observed for this MCPOIDCConfig.", + "format": "int64", + "type": "integer" + }, + "referenceCount": { + "description": "ReferenceCount is the number of workloads referencing this config.", + "format": "int32", + "type": "integer" + }, + "referencingWorkloads": { + "description": "ReferencingWorkloads is a list of workload resources that reference this MCPOIDCConfig.\nEach entry identifies the workload by kind and name.", + "items": { + "description": "WorkloadReference identifies a workload that references a shared configuration resource.\nNamespace is implicit \u2014 cross-namespace references are not supported.", + "properties": { + "kind": { + "description": "Kind is the type of workload resource", + "enum": [ + "MCPServer", + "VirtualMCPServer", + "MCPRemoteProxy" + ], + "type": "string" + }, + "name": { + "description": "Name is the name of the workload resource", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/mcpregistry_v1alpha1.json b/crdSchemas/mcpregistry_v1alpha1.json new file mode 100644 index 0000000..fd95beb --- /dev/null +++ b/crdSchemas/mcpregistry_v1alpha1.json @@ -0,0 +1,196 @@ +{ + "description": "MCPRegistry is the deprecated v1alpha1 version of the MCPRegistry resource.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPRegistrySpec defines the desired state of MCPRegistry", + "properties": { + "configYAML": { + "description": "ConfigYAML is the complete registry server config.yaml content.\nThe operator creates a ConfigMap from this string and mounts it\nat /config/config.yaml in the registry-api container.\nThe operator does NOT parse, validate, or transform this content \u2014\nconfiguration validation is the registry server's responsibility.\n\nSecurity note: this content is stored in a ConfigMap, not a Secret.\nDo not inline credentials (passwords, tokens, client secrets) in this\nfield. Instead, reference credentials via file paths and mount the\nactual secrets using the Volumes and VolumeMounts fields. For database\npasswords, use PGPassSecretRef.", + "minLength": 1, + "type": "string" + }, + "displayName": { + "description": "DisplayName is a human-readable name for the registry.", + "type": "string" + }, + "imagePullSecrets": { + "description": "ImagePullSecrets allows specifying image pull secrets for the registry API workload.\nThese are applied to both the registry-api Deployment's PodSpec.ImagePullSecrets\nand to the operator-managed ServiceAccount the registry API runs as, so private\nimages are pullable through either path.\n\nUse this field for new manifests.\n\nImportant: this is the ONLY way to attach image-pull credentials to the\noperator-managed ServiceAccount. The legacy\nspec.podTemplateSpec.spec.imagePullSecrets path populates the Deployment's pod\nspec ONLY \u2014 it does NOT touch the ServiceAccount. On managed Kubernetes\nplatforms that rely on ServiceAccount-level credential injection (for example\nGKE Workload Identity, OpenShift's per-SA dockercfg secrets, EKS IRSA), using\nonly the legacy PodTemplateSpec path can fail to pull private images even when\nthe secret exists in the namespace. Always set spec.imagePullSecrets when\nSA-level credentials matter.\n\nPrecedence with PodTemplateSpec:\n - This field is applied first as the controller-generated default.\n - Values set under spec.podTemplateSpec.spec.imagePullSecrets are user overrides\n and win on overlap. If the user supplies imagePullSecrets via PodTemplateSpec,\n those replace the default list on the Deployment (the list is treated atomically).\n - The ServiceAccount is always populated from this field \u2014 PodTemplateSpec does not\n affect the ServiceAccount.\n\nAn omitted field and an explicitly empty list are equivalent: both leave the\nServiceAccount's existing ImagePullSecrets unchanged. This preserves\nplatform-managed pull secrets (for example OpenShift's per-SA dockercfg\nentries) when overlays or patches emit an empty list. Truly clearing the\nServiceAccount's pull secrets requires recreating the resource.", + "items": { + "description": "LocalObjectReference contains enough information to let you locate the\nreferenced object inside the same namespace.", + "properties": { + "name": { + "default": "", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + } + }, + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "pgpassSecretRef": { + "description": "PGPassSecretRef references a Secret containing a pre-created pgpass file.\n\nWhy this is a dedicated field instead of a regular volume/volumeMount:\nPostgreSQL's libpq rejects pgpass files that aren't mode 0600. Kubernetes\nsecret volumes mount files as root-owned, and the registry-api container\nruns as non-root (UID 65532). A root-owned 0600 file is unreadable by\nUID 65532, and using fsGroup changes permissions to 0640 which libpq also\nrejects. The only solution is an init container that copies the file to an\nemptyDir as the app user and runs chmod 0600. This cannot be expressed\nthrough volumes/volumeMounts alone -- it requires an init container, two\nextra volumes (secret + emptyDir), a subPath mount, and an environment\nvariable, all wired together correctly.\n\nWhen specified, the operator generates all of that plumbing invisibly.\nThe user creates the Secret with pgpass-formatted content; the operator\nhandles only the Kubernetes permission mechanics.\n\nExample Secret:\n\n\tapiVersion: v1\n\tkind: Secret\n\tmetadata:\n\t name: my-pgpass\n\tstringData:\n\t .pgpass: |\n\t postgres:5432:registry:db_app:mypassword\n\t postgres:5432:registry:db_migrator:otherpassword\n\nThen reference it:\n\n\tpgpassSecretRef:\n\t name: my-pgpass\n\t key: .pgpass", + "properties": { + "key": { + "description": "The key of the secret to select from. Must be a valid secret key.", + "type": "string" + }, + "name": { + "default": "", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + }, + "optional": { + "description": "Specify whether the Secret or its key must be defined", + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + }, + "podTemplateSpec": { + "description": "PodTemplateSpec defines the pod template to use for the registry API server.\nThis allows for customizing the pod configuration beyond what is provided by the other fields.\nNote that to modify the specific container the registry API server runs in, you must specify\nthe `registry-api` container name in the PodTemplateSpec.\nThis field accepts a PodTemplateSpec object as JSON/YAML.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "volumeMounts": { + "description": "VolumeMounts defines additional volume mounts for the registry-api container.\nEach entry is a standard Kubernetes VolumeMount object (JSON/YAML).\nThe operator appends them to the container's volume mounts alongside the config mount.\n\nMount paths must match the file paths referenced in configYAML.\nFor example, if configYAML references passwordFile: /secrets/git-creds/token,\na corresponding volume mount must exist with mountPath: /secrets/git-creds.", + "items": { + "x-kubernetes-preserve-unknown-fields": true + }, + "type": "array", + "x-kubernetes-list-type": "atomic", + "x-kubernetes-preserve-unknown-fields": true + }, + "volumes": { + "description": "Volumes defines additional volumes to add to the registry API pod.\nEach entry is a standard Kubernetes Volume object (JSON/YAML).\nThe operator appends them to the pod spec alongside its own config volume.\n\nUse these to mount:\n - Secrets (git auth tokens, OAuth client secrets, CA certs)\n - ConfigMaps (registry data files)\n - PersistentVolumeClaims (registry data on persistent storage)\n - Any other volume type the registry server needs", + "items": { + "x-kubernetes-preserve-unknown-fields": true + }, + "type": "array", + "x-kubernetes-list-type": "atomic", + "x-kubernetes-preserve-unknown-fields": true + } + }, + "required": [ + "configYAML" + ], + "type": "object", + "additionalProperties": false + }, + "status": { + "description": "MCPRegistryStatus defines the observed state of MCPRegistry", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations of the MCPRegistry's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "message": { + "description": "Message provides additional information about the current phase", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration reflects the generation most recently observed by the controller", + "format": "int64", + "type": "integer" + }, + "phase": { + "description": "Phase represents the current overall phase of the MCPRegistry", + "enum": [ + "Pending", + "Ready", + "Failed", + "Terminating" + ], + "type": "string" + }, + "readyReplicas": { + "description": "ReadyReplicas is the number of ready registry API replicas", + "format": "int32", + "type": "integer" + }, + "url": { + "description": "URL is the URL where the registry API can be accessed", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/mcpregistry_v1beta1.json b/crdSchemas/mcpregistry_v1beta1.json new file mode 100644 index 0000000..4e343fa --- /dev/null +++ b/crdSchemas/mcpregistry_v1beta1.json @@ -0,0 +1,196 @@ +{ + "description": "MCPRegistry is the Schema for the mcpregistries API", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPRegistrySpec defines the desired state of MCPRegistry", + "properties": { + "configYAML": { + "description": "ConfigYAML is the complete registry server config.yaml content.\nThe operator creates a ConfigMap from this string and mounts it\nat /config/config.yaml in the registry-api container.\nThe operator does NOT parse, validate, or transform this content \u2014\nconfiguration validation is the registry server's responsibility.\n\nSecurity note: this content is stored in a ConfigMap, not a Secret.\nDo not inline credentials (passwords, tokens, client secrets) in this\nfield. Instead, reference credentials via file paths and mount the\nactual secrets using the Volumes and VolumeMounts fields. For database\npasswords, use PGPassSecretRef.", + "minLength": 1, + "type": "string" + }, + "displayName": { + "description": "DisplayName is a human-readable name for the registry.", + "type": "string" + }, + "imagePullSecrets": { + "description": "ImagePullSecrets allows specifying image pull secrets for the registry API workload.\nThese are applied to both the registry-api Deployment's PodSpec.ImagePullSecrets\nand to the operator-managed ServiceAccount the registry API runs as, so private\nimages are pullable through either path.\n\nUse this field for new manifests.\n\nImportant: this is the ONLY way to attach image-pull credentials to the\noperator-managed ServiceAccount. The legacy\nspec.podTemplateSpec.spec.imagePullSecrets path populates the Deployment's pod\nspec ONLY \u2014 it does NOT touch the ServiceAccount. On managed Kubernetes\nplatforms that rely on ServiceAccount-level credential injection (for example\nGKE Workload Identity, OpenShift's per-SA dockercfg secrets, EKS IRSA), using\nonly the legacy PodTemplateSpec path can fail to pull private images even when\nthe secret exists in the namespace. Always set spec.imagePullSecrets when\nSA-level credentials matter.\n\nPrecedence with PodTemplateSpec:\n - This field is applied first as the controller-generated default.\n - Values set under spec.podTemplateSpec.spec.imagePullSecrets are user overrides\n and win on overlap. If the user supplies imagePullSecrets via PodTemplateSpec,\n those replace the default list on the Deployment (the list is treated atomically).\n - The ServiceAccount is always populated from this field \u2014 PodTemplateSpec does not\n affect the ServiceAccount.\n\nAn omitted field and an explicitly empty list are equivalent: both leave the\nServiceAccount's existing ImagePullSecrets unchanged. This preserves\nplatform-managed pull secrets (for example OpenShift's per-SA dockercfg\nentries) when overlays or patches emit an empty list. Truly clearing the\nServiceAccount's pull secrets requires recreating the resource.", + "items": { + "description": "LocalObjectReference contains enough information to let you locate the\nreferenced object inside the same namespace.", + "properties": { + "name": { + "default": "", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + } + }, + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "pgpassSecretRef": { + "description": "PGPassSecretRef references a Secret containing a pre-created pgpass file.\n\nWhy this is a dedicated field instead of a regular volume/volumeMount:\nPostgreSQL's libpq rejects pgpass files that aren't mode 0600. Kubernetes\nsecret volumes mount files as root-owned, and the registry-api container\nruns as non-root (UID 65532). A root-owned 0600 file is unreadable by\nUID 65532, and using fsGroup changes permissions to 0640 which libpq also\nrejects. The only solution is an init container that copies the file to an\nemptyDir as the app user and runs chmod 0600. This cannot be expressed\nthrough volumes/volumeMounts alone -- it requires an init container, two\nextra volumes (secret + emptyDir), a subPath mount, and an environment\nvariable, all wired together correctly.\n\nWhen specified, the operator generates all of that plumbing invisibly.\nThe user creates the Secret with pgpass-formatted content; the operator\nhandles only the Kubernetes permission mechanics.\n\nExample Secret:\n\n\tapiVersion: v1\n\tkind: Secret\n\tmetadata:\n\t name: my-pgpass\n\tstringData:\n\t .pgpass: |\n\t postgres:5432:registry:db_app:mypassword\n\t postgres:5432:registry:db_migrator:otherpassword\n\nThen reference it:\n\n\tpgpassSecretRef:\n\t name: my-pgpass\n\t key: .pgpass", + "properties": { + "key": { + "description": "The key of the secret to select from. Must be a valid secret key.", + "type": "string" + }, + "name": { + "default": "", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + }, + "optional": { + "description": "Specify whether the Secret or its key must be defined", + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + }, + "podTemplateSpec": { + "description": "PodTemplateSpec defines the pod template to use for the registry API server.\nThis allows for customizing the pod configuration beyond what is provided by the other fields.\nNote that to modify the specific container the registry API server runs in, you must specify\nthe `registry-api` container name in the PodTemplateSpec.\nThis field accepts a PodTemplateSpec object as JSON/YAML.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "volumeMounts": { + "description": "VolumeMounts defines additional volume mounts for the registry-api container.\nEach entry is a standard Kubernetes VolumeMount object (JSON/YAML).\nThe operator appends them to the container's volume mounts alongside the config mount.\n\nMount paths must match the file paths referenced in configYAML.\nFor example, if configYAML references passwordFile: /secrets/git-creds/token,\na corresponding volume mount must exist with mountPath: /secrets/git-creds.", + "items": { + "x-kubernetes-preserve-unknown-fields": true + }, + "type": "array", + "x-kubernetes-list-type": "atomic", + "x-kubernetes-preserve-unknown-fields": true + }, + "volumes": { + "description": "Volumes defines additional volumes to add to the registry API pod.\nEach entry is a standard Kubernetes Volume object (JSON/YAML).\nThe operator appends them to the pod spec alongside its own config volume.\n\nUse these to mount:\n - Secrets (git auth tokens, OAuth client secrets, CA certs)\n - ConfigMaps (registry data files)\n - PersistentVolumeClaims (registry data on persistent storage)\n - Any other volume type the registry server needs", + "items": { + "x-kubernetes-preserve-unknown-fields": true + }, + "type": "array", + "x-kubernetes-list-type": "atomic", + "x-kubernetes-preserve-unknown-fields": true + } + }, + "required": [ + "configYAML" + ], + "type": "object", + "additionalProperties": false + }, + "status": { + "description": "MCPRegistryStatus defines the observed state of MCPRegistry", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations of the MCPRegistry's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "message": { + "description": "Message provides additional information about the current phase", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration reflects the generation most recently observed by the controller", + "format": "int64", + "type": "integer" + }, + "phase": { + "description": "Phase represents the current overall phase of the MCPRegistry", + "enum": [ + "Pending", + "Ready", + "Failed", + "Terminating" + ], + "type": "string" + }, + "readyReplicas": { + "description": "ReadyReplicas is the number of ready registry API replicas", + "format": "int32", + "type": "integer" + }, + "url": { + "description": "URL is the URL where the registry API can be accessed", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/mcpremoteproxy_v1alpha1.json b/crdSchemas/mcpremoteproxy_v1alpha1.json new file mode 100644 index 0000000..5c4d880 --- /dev/null +++ b/crdSchemas/mcpremoteproxy_v1alpha1.json @@ -0,0 +1,621 @@ +{ + "description": "MCPRemoteProxy is the deprecated v1alpha1 version of the MCPRemoteProxy resource.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPRemoteProxySpec defines the desired state of MCPRemoteProxy", + "properties": { + "audit": { + "description": "Audit defines audit logging configuration for the proxy", + "properties": { + "enabled": { + "default": false, + "description": "Enabled controls whether audit logging is enabled\nWhen true, enables audit logging with default configuration", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "authServerRef": { + "description": "AuthServerRef optionally references a resource that configures an embedded\nOAuth 2.0/OIDC authorization server to authenticate MCP clients.\nCurrently the only supported kind is MCPExternalAuthConfig (type: embeddedAuthServer).", + "properties": { + "kind": { + "default": "MCPExternalAuthConfig", + "description": "Kind identifies the type of the referenced resource.", + "enum": [ + "MCPExternalAuthConfig" + ], + "type": "string" + }, + "name": { + "description": "Name is the name of the referenced resource in the same namespace.", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "authzConfig": { + "description": "AuthzConfig defines authorization policy configuration for the proxy", + "properties": { + "configMap": { + "description": "ConfigMap references a ConfigMap containing authorization configuration\nOnly used when Type is \"configMap\"", + "properties": { + "key": { + "default": "authz.json", + "description": "Key is the key in the ConfigMap that contains the authorization configuration", + "type": "string" + }, + "name": { + "description": "Name is the name of the ConfigMap", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "groupClaimName": { + "description": "GroupClaimName is the JWT claim key that contains group membership for the\nprincipal. When set, takes priority over the well-known defaults\n(\"groups\", \"roles\", \"cognito:groups\"). Use this for IDPs that place\ngroups under a URI-style claim (e.g. \"https://example.com/groups\"). When\nType is \"configMap\", a group_claim_name entry in the referenced ConfigMap\nis overridden by this field if both are set.", + "maxLength": 253, + "type": "string" + }, + "groupEntityType": { + "description": "GroupEntityType is the Cedar entity type name used for principal parent\nUIDs synthesised from JWT group/role claims. Defaults to \"THVGroup\" when\nempty. Must match the entity type used in the static entity store for\ntransitive `in` checks (e.g. `ClaimGroup \u2192 PlatformRole`) to resolve.\nNamespaced names (`Foo::Bar`) are not yet supported. When Type is\n\"configMap\", a group_entity_type entry in the referenced ConfigMap is\noverridden by this field if both are set.", + "maxLength": 63, + "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", + "type": "string" + }, + "inline": { + "description": "Inline contains direct authorization configuration\nOnly used when Type is \"inline\"", + "properties": { + "entitiesJson": { + "default": "[]", + "description": "EntitiesJSON is a JSON string representing Cedar entities. Required when\ntransitive policies (e.g. `ClaimGroup \u2192 PlatformRole`) need a static\nentity store; defaults to \"[]\".", + "type": "string" + }, + "policies": { + "description": "Policies is a list of Cedar policy strings", + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "primaryUpstreamProvider": { + "description": "PrimaryUpstreamProvider names the upstream IDP whose access token's\nclaims Cedar should evaluate.\n\nDeprecated: on VirtualMCPServer this field has moved to\nspec.authServerConfig.primaryUpstreamProvider. The old location is\nstill read for one release for backward compatibility; the\nVirtualMCPServer controller emits an AuthzPrimaryUpstreamProviderDeprecated\nWarning event whenever it is consumed, and removal is planned for the\nrelease after the deprecation cycle.\n\nOn MCPServer and MCPRemoteProxy this field has always been a structural\nno-op (those CRDs do not run an embedded auth server). Setting it\ncontinues to surface the AuthzPrimaryUpstreamProviderIgnored advisory\ncondition; the deprecation does not change that behaviour.", + "maxLength": 63, + "minLength": 1, + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "type": "string" + } + }, + "required": [ + "policies" + ], + "type": "object", + "additionalProperties": false + }, + "roleClaimName": { + "description": "RoleClaimName is the JWT claim key that contains role membership for the\nprincipal. When set, the claim is extracted separately from GroupClaimName\nand both are mapped to the configured GroupEntityType. When Type is\n\"configMap\", a role_claim_name entry in the referenced ConfigMap is\noverridden by this field if both are set.", + "maxLength": 253, + "type": "string" + }, + "type": { + "default": "configMap", + "description": "Type is the type of authorization configuration", + "enum": [ + "configMap", + "inline" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "configMap must be set when type is 'configMap', and must not be set otherwise", + "rule": "self.type == 'configMap' ? has(self.configMap) : !has(self.configMap)" + }, + { + "message": "inline must be set when type is 'inline', and must not be set otherwise", + "rule": "self.type == 'inline' ? has(self.inline) : !has(self.inline)" + } + ], + "additionalProperties": false + }, + "endpointPrefix": { + "description": "EndpointPrefix is the path prefix to prepend to SSE endpoint URLs.\nThis is used to handle path-based ingress routing scenarios where the ingress\nstrips a path prefix before forwarding to the backend.", + "type": "string" + }, + "externalAuthConfigRef": { + "description": "ExternalAuthConfigRef references a MCPExternalAuthConfig resource for token exchange.\nWhen specified, the proxy will exchange validated incoming tokens for remote service tokens.\nThe referenced MCPExternalAuthConfig must exist in the same namespace as this MCPRemoteProxy.", + "properties": { + "name": { + "description": "Name is the name of the MCPExternalAuthConfig resource", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "groupRef": { + "description": "GroupRef references the MCPGroup this proxy belongs to.\nThe referenced MCPGroup must be in the same namespace.", + "properties": { + "name": { + "description": "Name is the name of the MCPGroup resource in the same namespace", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "headerForward": { + "description": "HeaderForward configures headers to inject into requests to the remote MCP server.\nUse this to add custom headers like X-Tenant-ID or correlation IDs.", + "properties": { + "addHeadersFromSecret": { + "description": "AddHeadersFromSecret references Kubernetes Secrets for sensitive header values.", + "items": { + "description": "HeaderFromSecret defines a header whose value comes from a Kubernetes Secret.", + "properties": { + "headerName": { + "description": "HeaderName is the HTTP header name (e.g., \"X-API-Key\")", + "maxLength": 255, + "minLength": 1, + "type": "string" + }, + "valueSecretRef": { + "description": "ValueSecretRef references the Secret and key containing the header value", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "headerName", + "valueSecretRef" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "headerName" + ], + "x-kubernetes-list-type": "map" + }, + "addPlaintextHeaders": { + "additionalProperties": { + "type": "string" + }, + "description": "AddPlaintextHeaders is a map of header names to literal values to inject into requests.\nWARNING: Values are stored in plaintext and visible via kubectl commands.\nUse addHeadersFromSecret for sensitive data like API keys or tokens.", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + }, + "oidcConfigRef": { + "description": "OIDCConfigRef references a shared MCPOIDCConfig resource for OIDC authentication.\nThe referenced MCPOIDCConfig must exist in the same namespace as this MCPRemoteProxy.\nPer-server overrides (audience, scopes) are specified here; shared provider config\nlives in the MCPOIDCConfig resource.", + "properties": { + "audience": { + "description": "Audience is the expected audience for token validation.\nThis MUST be unique per server to prevent token replay attacks.", + "minLength": 1, + "type": "string" + }, + "name": { + "description": "Name is the name of the MCPOIDCConfig resource", + "minLength": 1, + "type": "string" + }, + "resourceUrl": { + "description": "ResourceURL is the public URL for OAuth protected resource metadata (RFC 9728).\nWhen the server is exposed via Ingress or gateway, set this to the external\nURL that MCP clients connect to. If not specified, defaults to the internal\nKubernetes service URL.", + "type": "string" + }, + "scopes": { + "description": "Scopes is the list of OAuth scopes to advertise in the well-known endpoint (RFC 9728).\nIf empty, defaults to [\"openid\"].", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "required": [ + "audience", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "proxyPort": { + "default": 8080, + "description": "ProxyPort is the port to expose the MCP proxy on", + "format": "int32", + "maximum": 65535, + "minimum": 1, + "type": "integer" + }, + "remoteUrl": { + "description": "RemoteURL is the URL of the remote MCP server to proxy", + "pattern": "^https?://", + "type": "string" + }, + "resourceOverrides": { + "description": "ResourceOverrides allows overriding annotations and labels for resources created by the operator", + "properties": { + "proxyDeployment": { + "description": "ProxyDeployment defines overrides for the Proxy Deployment resource (toolhive proxy)", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "env": { + "description": "Env are environment variables to set in the proxy container (thv run process)\nThese affect the toolhive proxy itself, not the MCP server it manages\nUse TOOLHIVE_DEBUG=true to enable debug logging in the proxy", + "items": { + "description": "EnvVar represents an environment variable in a container", + "properties": { + "name": { + "description": "Name of the environment variable", + "type": "string" + }, + "value": { + "description": "Value of the environment variable", + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + }, + "imagePullSecrets": { + "description": "ImagePullSecrets allows specifying image pull secrets for the proxy runner\nThese are applied to both the Deployment and the ServiceAccount", + "items": { + "description": "LocalObjectReference contains enough information to let you locate the\nreferenced object inside the same namespace.", + "properties": { + "name": { + "default": "", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + } + }, + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + }, + "podTemplateMetadataOverrides": { + "description": "ResourceMetadataOverrides defines metadata overrides for a resource", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "proxyService": { + "description": "ProxyService defines overrides for the Proxy Service resource (points to the proxy deployment)", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "resources": { + "description": "Resources defines the resource requirements for the proxy container", + "properties": { + "limits": { + "description": "Limits describes the maximum amount of compute resources allowed", + "properties": { + "cpu": { + "description": "CPU is the CPU limit in cores (e.g., \"500m\" for 0.5 cores)", + "type": "string" + }, + "memory": { + "description": "Memory is the memory limit in bytes (e.g., \"64Mi\" for 64 megabytes)", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "requests": { + "description": "Requests describes the minimum amount of compute resources required", + "properties": { + "cpu": { + "description": "CPU is the CPU limit in cores (e.g., \"500m\" for 0.5 cores)", + "type": "string" + }, + "memory": { + "description": "Memory is the memory limit in bytes (e.g., \"64Mi\" for 64 megabytes)", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "serviceAccount": { + "description": "ServiceAccount is the name of an already existing service account to use by the proxy.\nIf not specified, a ServiceAccount will be created automatically and used by the proxy.", + "type": "string" + }, + "sessionAffinity": { + "default": "ClientIP", + "description": "SessionAffinity controls whether the Service routes repeated client connections to the same pod.\nMCP protocols (SSE, streamable-http) are stateful, so ClientIP is the default.\nSet to \"None\" for stateless servers or when using an external load balancer with its own affinity.", + "enum": [ + "ClientIP", + "None" + ], + "type": "string" + }, + "telemetryConfigRef": { + "description": "TelemetryConfigRef references an MCPTelemetryConfig resource for shared telemetry configuration.\nThe referenced MCPTelemetryConfig must exist in the same namespace as this MCPRemoteProxy.\nCross-namespace references are not supported for security and isolation reasons.", + "properties": { + "name": { + "description": "Name is the name of the MCPTelemetryConfig resource", + "minLength": 1, + "type": "string" + }, + "serviceName": { + "description": "ServiceName overrides the telemetry service name for this specific server.\nThis MUST be unique per server for proper observability (e.g., distinguishing\ntraces and metrics from different servers sharing the same collector).\nIf empty, defaults to the server name with \"thv-\" prefix at runtime.", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "toolConfigRef": { + "description": "ToolConfigRef references a MCPToolConfig resource for tool filtering and renaming.\nThe referenced MCPToolConfig must exist in the same namespace as this MCPRemoteProxy.\nCross-namespace references are not supported for security and isolation reasons.\nIf specified, this allows filtering and overriding tools from the remote MCP server.", + "properties": { + "name": { + "description": "Name is the name of the MCPToolConfig resource in the same namespace", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "transport": { + "default": "streamable-http", + "description": "Transport is the transport method for the remote proxy (sse or streamable-http)", + "enum": [ + "sse", + "streamable-http" + ], + "type": "string" + }, + "trustProxyHeaders": { + "default": false, + "description": "TrustProxyHeaders indicates whether to trust X-Forwarded-* headers from reverse proxies\nWhen enabled, the proxy will use X-Forwarded-Proto, X-Forwarded-Host, X-Forwarded-Port,\nand X-Forwarded-Prefix headers to construct endpoint URLs", + "type": "boolean" + } + }, + "required": [ + "remoteUrl" + ], + "type": "object", + "additionalProperties": false + }, + "status": { + "description": "MCPRemoteProxyStatus defines the observed state of MCPRemoteProxy", + "properties": { + "authServerConfigHash": { + "description": "AuthServerConfigHash is the hash of the referenced authServerRef spec,\nused to detect configuration changes and trigger reconciliation.", + "type": "string" + }, + "conditions": { + "description": "Conditions represent the latest available observations of the MCPRemoteProxy's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "externalAuthConfigHash": { + "description": "ExternalAuthConfigHash is the hash of the referenced MCPExternalAuthConfig spec", + "type": "string" + }, + "externalUrl": { + "description": "ExternalURL is the external URL where the proxy can be accessed (if exposed externally)", + "type": "string" + }, + "message": { + "description": "Message provides additional information about the current phase", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration reflects the generation of the most recently observed MCPRemoteProxy", + "format": "int64", + "type": "integer" + }, + "oidcConfigHash": { + "description": "OIDCConfigHash is the hash of the referenced MCPOIDCConfig spec for change detection", + "type": "string" + }, + "phase": { + "description": "Phase is the current phase of the MCPRemoteProxy", + "enum": [ + "Pending", + "Ready", + "Failed", + "Terminating" + ], + "type": "string" + }, + "telemetryConfigHash": { + "description": "TelemetryConfigHash stores the hash of the referenced MCPTelemetryConfig for change detection", + "type": "string" + }, + "toolConfigHash": { + "description": "ToolConfigHash stores the hash of the referenced ToolConfig for change detection", + "type": "string" + }, + "url": { + "description": "URL is the internal cluster URL where the proxy can be accessed", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/mcpremoteproxy_v1beta1.json b/crdSchemas/mcpremoteproxy_v1beta1.json new file mode 100644 index 0000000..1ed29ab --- /dev/null +++ b/crdSchemas/mcpremoteproxy_v1beta1.json @@ -0,0 +1,621 @@ +{ + "description": "MCPRemoteProxy is the Schema for the mcpremoteproxies API\nIt enables proxying remote MCP servers with authentication, authorization, audit logging, and tool filtering", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPRemoteProxySpec defines the desired state of MCPRemoteProxy", + "properties": { + "audit": { + "description": "Audit defines audit logging configuration for the proxy", + "properties": { + "enabled": { + "default": false, + "description": "Enabled controls whether audit logging is enabled\nWhen true, enables audit logging with default configuration", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "authServerRef": { + "description": "AuthServerRef optionally references a resource that configures an embedded\nOAuth 2.0/OIDC authorization server to authenticate MCP clients.\nCurrently the only supported kind is MCPExternalAuthConfig (type: embeddedAuthServer).", + "properties": { + "kind": { + "default": "MCPExternalAuthConfig", + "description": "Kind identifies the type of the referenced resource.", + "enum": [ + "MCPExternalAuthConfig" + ], + "type": "string" + }, + "name": { + "description": "Name is the name of the referenced resource in the same namespace.", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "authzConfig": { + "description": "AuthzConfig defines authorization policy configuration for the proxy", + "properties": { + "configMap": { + "description": "ConfigMap references a ConfigMap containing authorization configuration\nOnly used when Type is \"configMap\"", + "properties": { + "key": { + "default": "authz.json", + "description": "Key is the key in the ConfigMap that contains the authorization configuration", + "type": "string" + }, + "name": { + "description": "Name is the name of the ConfigMap", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "groupClaimName": { + "description": "GroupClaimName is the JWT claim key that contains group membership for the\nprincipal. When set, takes priority over the well-known defaults\n(\"groups\", \"roles\", \"cognito:groups\"). Use this for IDPs that place\ngroups under a URI-style claim (e.g. \"https://example.com/groups\"). When\nType is \"configMap\", a group_claim_name entry in the referenced ConfigMap\nis overridden by this field if both are set.", + "maxLength": 253, + "type": "string" + }, + "groupEntityType": { + "description": "GroupEntityType is the Cedar entity type name used for principal parent\nUIDs synthesised from JWT group/role claims. Defaults to \"THVGroup\" when\nempty. Must match the entity type used in the static entity store for\ntransitive `in` checks (e.g. `ClaimGroup \u2192 PlatformRole`) to resolve.\nNamespaced names (`Foo::Bar`) are not yet supported. When Type is\n\"configMap\", a group_entity_type entry in the referenced ConfigMap is\noverridden by this field if both are set.", + "maxLength": 63, + "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", + "type": "string" + }, + "inline": { + "description": "Inline contains direct authorization configuration\nOnly used when Type is \"inline\"", + "properties": { + "entitiesJson": { + "default": "[]", + "description": "EntitiesJSON is a JSON string representing Cedar entities. Required when\ntransitive policies (e.g. `ClaimGroup \u2192 PlatformRole`) need a static\nentity store; defaults to \"[]\".", + "type": "string" + }, + "policies": { + "description": "Policies is a list of Cedar policy strings", + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "primaryUpstreamProvider": { + "description": "PrimaryUpstreamProvider names the upstream IDP whose access token's\nclaims Cedar should evaluate.\n\nDeprecated: on VirtualMCPServer this field has moved to\nspec.authServerConfig.primaryUpstreamProvider. The old location is\nstill read for one release for backward compatibility; the\nVirtualMCPServer controller emits an AuthzPrimaryUpstreamProviderDeprecated\nWarning event whenever it is consumed, and removal is planned for the\nrelease after the deprecation cycle.\n\nOn MCPServer and MCPRemoteProxy this field has always been a structural\nno-op (those CRDs do not run an embedded auth server). Setting it\ncontinues to surface the AuthzPrimaryUpstreamProviderIgnored advisory\ncondition; the deprecation does not change that behaviour.", + "maxLength": 63, + "minLength": 1, + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "type": "string" + } + }, + "required": [ + "policies" + ], + "type": "object", + "additionalProperties": false + }, + "roleClaimName": { + "description": "RoleClaimName is the JWT claim key that contains role membership for the\nprincipal. When set, the claim is extracted separately from GroupClaimName\nand both are mapped to the configured GroupEntityType. When Type is\n\"configMap\", a role_claim_name entry in the referenced ConfigMap is\noverridden by this field if both are set.", + "maxLength": 253, + "type": "string" + }, + "type": { + "default": "configMap", + "description": "Type is the type of authorization configuration", + "enum": [ + "configMap", + "inline" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "configMap must be set when type is 'configMap', and must not be set otherwise", + "rule": "self.type == 'configMap' ? has(self.configMap) : !has(self.configMap)" + }, + { + "message": "inline must be set when type is 'inline', and must not be set otherwise", + "rule": "self.type == 'inline' ? has(self.inline) : !has(self.inline)" + } + ], + "additionalProperties": false + }, + "endpointPrefix": { + "description": "EndpointPrefix is the path prefix to prepend to SSE endpoint URLs.\nThis is used to handle path-based ingress routing scenarios where the ingress\nstrips a path prefix before forwarding to the backend.", + "type": "string" + }, + "externalAuthConfigRef": { + "description": "ExternalAuthConfigRef references a MCPExternalAuthConfig resource for token exchange.\nWhen specified, the proxy will exchange validated incoming tokens for remote service tokens.\nThe referenced MCPExternalAuthConfig must exist in the same namespace as this MCPRemoteProxy.", + "properties": { + "name": { + "description": "Name is the name of the MCPExternalAuthConfig resource", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "groupRef": { + "description": "GroupRef references the MCPGroup this proxy belongs to.\nThe referenced MCPGroup must be in the same namespace.", + "properties": { + "name": { + "description": "Name is the name of the MCPGroup resource in the same namespace", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "headerForward": { + "description": "HeaderForward configures headers to inject into requests to the remote MCP server.\nUse this to add custom headers like X-Tenant-ID or correlation IDs.", + "properties": { + "addHeadersFromSecret": { + "description": "AddHeadersFromSecret references Kubernetes Secrets for sensitive header values.", + "items": { + "description": "HeaderFromSecret defines a header whose value comes from a Kubernetes Secret.", + "properties": { + "headerName": { + "description": "HeaderName is the HTTP header name (e.g., \"X-API-Key\")", + "maxLength": 255, + "minLength": 1, + "type": "string" + }, + "valueSecretRef": { + "description": "ValueSecretRef references the Secret and key containing the header value", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "headerName", + "valueSecretRef" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "headerName" + ], + "x-kubernetes-list-type": "map" + }, + "addPlaintextHeaders": { + "additionalProperties": { + "type": "string" + }, + "description": "AddPlaintextHeaders is a map of header names to literal values to inject into requests.\nWARNING: Values are stored in plaintext and visible via kubectl commands.\nUse addHeadersFromSecret for sensitive data like API keys or tokens.", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + }, + "oidcConfigRef": { + "description": "OIDCConfigRef references a shared MCPOIDCConfig resource for OIDC authentication.\nThe referenced MCPOIDCConfig must exist in the same namespace as this MCPRemoteProxy.\nPer-server overrides (audience, scopes) are specified here; shared provider config\nlives in the MCPOIDCConfig resource.", + "properties": { + "audience": { + "description": "Audience is the expected audience for token validation.\nThis MUST be unique per server to prevent token replay attacks.", + "minLength": 1, + "type": "string" + }, + "name": { + "description": "Name is the name of the MCPOIDCConfig resource", + "minLength": 1, + "type": "string" + }, + "resourceUrl": { + "description": "ResourceURL is the public URL for OAuth protected resource metadata (RFC 9728).\nWhen the server is exposed via Ingress or gateway, set this to the external\nURL that MCP clients connect to. If not specified, defaults to the internal\nKubernetes service URL.", + "type": "string" + }, + "scopes": { + "description": "Scopes is the list of OAuth scopes to advertise in the well-known endpoint (RFC 9728).\nIf empty, defaults to [\"openid\"].", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "required": [ + "audience", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "proxyPort": { + "default": 8080, + "description": "ProxyPort is the port to expose the MCP proxy on", + "format": "int32", + "maximum": 65535, + "minimum": 1, + "type": "integer" + }, + "remoteUrl": { + "description": "RemoteURL is the URL of the remote MCP server to proxy", + "pattern": "^https?://", + "type": "string" + }, + "resourceOverrides": { + "description": "ResourceOverrides allows overriding annotations and labels for resources created by the operator", + "properties": { + "proxyDeployment": { + "description": "ProxyDeployment defines overrides for the Proxy Deployment resource (toolhive proxy)", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "env": { + "description": "Env are environment variables to set in the proxy container (thv run process)\nThese affect the toolhive proxy itself, not the MCP server it manages\nUse TOOLHIVE_DEBUG=true to enable debug logging in the proxy", + "items": { + "description": "EnvVar represents an environment variable in a container", + "properties": { + "name": { + "description": "Name of the environment variable", + "type": "string" + }, + "value": { + "description": "Value of the environment variable", + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + }, + "imagePullSecrets": { + "description": "ImagePullSecrets allows specifying image pull secrets for the proxy runner\nThese are applied to both the Deployment and the ServiceAccount", + "items": { + "description": "LocalObjectReference contains enough information to let you locate the\nreferenced object inside the same namespace.", + "properties": { + "name": { + "default": "", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + } + }, + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + }, + "podTemplateMetadataOverrides": { + "description": "ResourceMetadataOverrides defines metadata overrides for a resource", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "proxyService": { + "description": "ProxyService defines overrides for the Proxy Service resource (points to the proxy deployment)", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "resources": { + "description": "Resources defines the resource requirements for the proxy container", + "properties": { + "limits": { + "description": "Limits describes the maximum amount of compute resources allowed", + "properties": { + "cpu": { + "description": "CPU is the CPU limit in cores (e.g., \"500m\" for 0.5 cores)", + "type": "string" + }, + "memory": { + "description": "Memory is the memory limit in bytes (e.g., \"64Mi\" for 64 megabytes)", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "requests": { + "description": "Requests describes the minimum amount of compute resources required", + "properties": { + "cpu": { + "description": "CPU is the CPU limit in cores (e.g., \"500m\" for 0.5 cores)", + "type": "string" + }, + "memory": { + "description": "Memory is the memory limit in bytes (e.g., \"64Mi\" for 64 megabytes)", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "serviceAccount": { + "description": "ServiceAccount is the name of an already existing service account to use by the proxy.\nIf not specified, a ServiceAccount will be created automatically and used by the proxy.", + "type": "string" + }, + "sessionAffinity": { + "default": "ClientIP", + "description": "SessionAffinity controls whether the Service routes repeated client connections to the same pod.\nMCP protocols (SSE, streamable-http) are stateful, so ClientIP is the default.\nSet to \"None\" for stateless servers or when using an external load balancer with its own affinity.", + "enum": [ + "ClientIP", + "None" + ], + "type": "string" + }, + "telemetryConfigRef": { + "description": "TelemetryConfigRef references an MCPTelemetryConfig resource for shared telemetry configuration.\nThe referenced MCPTelemetryConfig must exist in the same namespace as this MCPRemoteProxy.\nCross-namespace references are not supported for security and isolation reasons.", + "properties": { + "name": { + "description": "Name is the name of the MCPTelemetryConfig resource", + "minLength": 1, + "type": "string" + }, + "serviceName": { + "description": "ServiceName overrides the telemetry service name for this specific server.\nThis MUST be unique per server for proper observability (e.g., distinguishing\ntraces and metrics from different servers sharing the same collector).\nIf empty, defaults to the server name with \"thv-\" prefix at runtime.", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "toolConfigRef": { + "description": "ToolConfigRef references a MCPToolConfig resource for tool filtering and renaming.\nThe referenced MCPToolConfig must exist in the same namespace as this MCPRemoteProxy.\nCross-namespace references are not supported for security and isolation reasons.\nIf specified, this allows filtering and overriding tools from the remote MCP server.", + "properties": { + "name": { + "description": "Name is the name of the MCPToolConfig resource in the same namespace", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "transport": { + "default": "streamable-http", + "description": "Transport is the transport method for the remote proxy (sse or streamable-http)", + "enum": [ + "sse", + "streamable-http" + ], + "type": "string" + }, + "trustProxyHeaders": { + "default": false, + "description": "TrustProxyHeaders indicates whether to trust X-Forwarded-* headers from reverse proxies\nWhen enabled, the proxy will use X-Forwarded-Proto, X-Forwarded-Host, X-Forwarded-Port,\nand X-Forwarded-Prefix headers to construct endpoint URLs", + "type": "boolean" + } + }, + "required": [ + "remoteUrl" + ], + "type": "object", + "additionalProperties": false + }, + "status": { + "description": "MCPRemoteProxyStatus defines the observed state of MCPRemoteProxy", + "properties": { + "authServerConfigHash": { + "description": "AuthServerConfigHash is the hash of the referenced authServerRef spec,\nused to detect configuration changes and trigger reconciliation.", + "type": "string" + }, + "conditions": { + "description": "Conditions represent the latest available observations of the MCPRemoteProxy's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "externalAuthConfigHash": { + "description": "ExternalAuthConfigHash is the hash of the referenced MCPExternalAuthConfig spec", + "type": "string" + }, + "externalUrl": { + "description": "ExternalURL is the external URL where the proxy can be accessed (if exposed externally)", + "type": "string" + }, + "message": { + "description": "Message provides additional information about the current phase", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration reflects the generation of the most recently observed MCPRemoteProxy", + "format": "int64", + "type": "integer" + }, + "oidcConfigHash": { + "description": "OIDCConfigHash is the hash of the referenced MCPOIDCConfig spec for change detection", + "type": "string" + }, + "phase": { + "description": "Phase is the current phase of the MCPRemoteProxy", + "enum": [ + "Pending", + "Ready", + "Failed", + "Terminating" + ], + "type": "string" + }, + "telemetryConfigHash": { + "description": "TelemetryConfigHash stores the hash of the referenced MCPTelemetryConfig for change detection", + "type": "string" + }, + "toolConfigHash": { + "description": "ToolConfigHash stores the hash of the referenced ToolConfig for change detection", + "type": "string" + }, + "url": { + "description": "URL is the internal cluster URL where the proxy can be accessed", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/mcpserver_v1alpha1.json b/crdSchemas/mcpserver_v1alpha1.json new file mode 100644 index 0000000..df715ae --- /dev/null +++ b/crdSchemas/mcpserver_v1alpha1.json @@ -0,0 +1,946 @@ +{ + "description": "MCPServer is the deprecated v1alpha1 version of the MCPServer resource.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPServerSpec defines the desired state of MCPServer", + "properties": { + "args": { + "description": "Args are additional arguments to pass to the MCP server", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "audit": { + "description": "Audit defines audit logging configuration for the MCP server", + "properties": { + "enabled": { + "default": false, + "description": "Enabled controls whether audit logging is enabled\nWhen true, enables audit logging with default configuration", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "authServerRef": { + "description": "AuthServerRef optionally references a resource that configures an embedded\nOAuth 2.0/OIDC authorization server to authenticate MCP clients.\nCurrently the only supported kind is MCPExternalAuthConfig (type: embeddedAuthServer).", + "properties": { + "kind": { + "default": "MCPExternalAuthConfig", + "description": "Kind identifies the type of the referenced resource.", + "enum": [ + "MCPExternalAuthConfig" + ], + "type": "string" + }, + "name": { + "description": "Name is the name of the referenced resource in the same namespace.", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "authzConfig": { + "description": "AuthzConfig defines authorization policy configuration for the MCP server", + "properties": { + "configMap": { + "description": "ConfigMap references a ConfigMap containing authorization configuration\nOnly used when Type is \"configMap\"", + "properties": { + "key": { + "default": "authz.json", + "description": "Key is the key in the ConfigMap that contains the authorization configuration", + "type": "string" + }, + "name": { + "description": "Name is the name of the ConfigMap", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "groupClaimName": { + "description": "GroupClaimName is the JWT claim key that contains group membership for the\nprincipal. When set, takes priority over the well-known defaults\n(\"groups\", \"roles\", \"cognito:groups\"). Use this for IDPs that place\ngroups under a URI-style claim (e.g. \"https://example.com/groups\"). When\nType is \"configMap\", a group_claim_name entry in the referenced ConfigMap\nis overridden by this field if both are set.", + "maxLength": 253, + "type": "string" + }, + "groupEntityType": { + "description": "GroupEntityType is the Cedar entity type name used for principal parent\nUIDs synthesised from JWT group/role claims. Defaults to \"THVGroup\" when\nempty. Must match the entity type used in the static entity store for\ntransitive `in` checks (e.g. `ClaimGroup \u2192 PlatformRole`) to resolve.\nNamespaced names (`Foo::Bar`) are not yet supported. When Type is\n\"configMap\", a group_entity_type entry in the referenced ConfigMap is\noverridden by this field if both are set.", + "maxLength": 63, + "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", + "type": "string" + }, + "inline": { + "description": "Inline contains direct authorization configuration\nOnly used when Type is \"inline\"", + "properties": { + "entitiesJson": { + "default": "[]", + "description": "EntitiesJSON is a JSON string representing Cedar entities. Required when\ntransitive policies (e.g. `ClaimGroup \u2192 PlatformRole`) need a static\nentity store; defaults to \"[]\".", + "type": "string" + }, + "policies": { + "description": "Policies is a list of Cedar policy strings", + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "primaryUpstreamProvider": { + "description": "PrimaryUpstreamProvider names the upstream IDP whose access token's\nclaims Cedar should evaluate.\n\nDeprecated: on VirtualMCPServer this field has moved to\nspec.authServerConfig.primaryUpstreamProvider. The old location is\nstill read for one release for backward compatibility; the\nVirtualMCPServer controller emits an AuthzPrimaryUpstreamProviderDeprecated\nWarning event whenever it is consumed, and removal is planned for the\nrelease after the deprecation cycle.\n\nOn MCPServer and MCPRemoteProxy this field has always been a structural\nno-op (those CRDs do not run an embedded auth server). Setting it\ncontinues to surface the AuthzPrimaryUpstreamProviderIgnored advisory\ncondition; the deprecation does not change that behaviour.", + "maxLength": 63, + "minLength": 1, + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "type": "string" + } + }, + "required": [ + "policies" + ], + "type": "object", + "additionalProperties": false + }, + "roleClaimName": { + "description": "RoleClaimName is the JWT claim key that contains role membership for the\nprincipal. When set, the claim is extracted separately from GroupClaimName\nand both are mapped to the configured GroupEntityType. When Type is\n\"configMap\", a role_claim_name entry in the referenced ConfigMap is\noverridden by this field if both are set.", + "maxLength": 253, + "type": "string" + }, + "type": { + "default": "configMap", + "description": "Type is the type of authorization configuration", + "enum": [ + "configMap", + "inline" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "configMap must be set when type is 'configMap', and must not be set otherwise", + "rule": "self.type == 'configMap' ? has(self.configMap) : !has(self.configMap)" + }, + { + "message": "inline must be set when type is 'inline', and must not be set otherwise", + "rule": "self.type == 'inline' ? has(self.inline) : !has(self.inline)" + } + ], + "additionalProperties": false + }, + "backendReplicas": { + "description": "BackendReplicas is the desired number of MCP server backend pod replicas.\nThis controls the backend Deployment (the MCP server container itself),\nindependent of the proxy runner controlled by Replicas.\nWhen nil, the operator does not set Deployment.Spec.Replicas, leaving replica\nmanagement to an HPA or other external controller.", + "format": "int32", + "minimum": 0, + "type": "integer" + }, + "endpointPrefix": { + "description": "EndpointPrefix is the path prefix to prepend to SSE endpoint URLs.\nThis is used to handle path-based ingress routing scenarios where the ingress\nstrips a path prefix before forwarding to the backend.", + "type": "string" + }, + "env": { + "description": "Env are environment variables to set in the MCP server container", + "items": { + "description": "EnvVar represents an environment variable in a container", + "properties": { + "name": { + "description": "Name of the environment variable", + "type": "string" + }, + "value": { + "description": "Value of the environment variable", + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + }, + "externalAuthConfigRef": { + "description": "ExternalAuthConfigRef references a MCPExternalAuthConfig resource for external authentication.\nThe referenced MCPExternalAuthConfig must exist in the same namespace as this MCPServer.", + "properties": { + "name": { + "description": "Name is the name of the MCPExternalAuthConfig resource", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "groupRef": { + "description": "GroupRef references the MCPGroup this server belongs to.\nThe referenced MCPGroup must be in the same namespace.", + "properties": { + "name": { + "description": "Name is the name of the MCPGroup resource in the same namespace", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "image": { + "description": "Image is the container image for the MCP server", + "type": "string" + }, + "mcpPort": { + "description": "MCPPort is the port that MCP server listens to", + "format": "int32", + "maximum": 65535, + "minimum": 1, + "type": "integer" + }, + "oidcConfigRef": { + "description": "OIDCConfigRef references a shared MCPOIDCConfig resource for OIDC authentication.\nThe referenced MCPOIDCConfig must exist in the same namespace as this MCPServer.\nPer-server overrides (audience, scopes) are specified here; shared provider config\nlives in the MCPOIDCConfig resource.", + "properties": { + "audience": { + "description": "Audience is the expected audience for token validation.\nThis MUST be unique per server to prevent token replay attacks.", + "minLength": 1, + "type": "string" + }, + "name": { + "description": "Name is the name of the MCPOIDCConfig resource", + "minLength": 1, + "type": "string" + }, + "resourceUrl": { + "description": "ResourceURL is the public URL for OAuth protected resource metadata (RFC 9728).\nWhen the server is exposed via Ingress or gateway, set this to the external\nURL that MCP clients connect to. If not specified, defaults to the internal\nKubernetes service URL.", + "type": "string" + }, + "scopes": { + "description": "Scopes is the list of OAuth scopes to advertise in the well-known endpoint (RFC 9728).\nIf empty, defaults to [\"openid\"].", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "required": [ + "audience", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "permissionProfile": { + "description": "PermissionProfile defines the permission profile to use", + "properties": { + "key": { + "description": "Key is the key in the ConfigMap that contains the permission profile\nOnly used when Type is \"configmap\"", + "type": "string" + }, + "name": { + "description": "Name is the name of the permission profile\nIf Type is \"builtin\", Name must be one of: \"none\", \"network\"\nIf Type is \"configmap\", Name is the name of the ConfigMap", + "type": "string" + }, + "type": { + "default": "builtin", + "description": "Type is the type of permission profile reference", + "enum": [ + "builtin", + "configmap" + ], + "type": "string" + } + }, + "required": [ + "name", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "podTemplateSpec": { + "description": "PodTemplateSpec defines the pod template to use for the MCP server\nThis allows for customizing the pod configuration beyond what is provided by the other fields.\nNote that to modify the specific container the MCP server runs in, you must specify\nthe `mcp` container name in the PodTemplateSpec.\nThis field accepts a PodTemplateSpec object as JSON/YAML.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "proxyMode": { + "default": "streamable-http", + "description": "ProxyMode is the proxy mode for stdio transport (sse or streamable-http)\nThis setting is ONLY applicable when Transport is \"stdio\".\nFor direct transports (sse, streamable-http), this field is ignored.\nThe default value is applied by Kubernetes but will be ignored for non-stdio transports.", + "enum": [ + "sse", + "streamable-http" + ], + "type": "string" + }, + "proxyPort": { + "default": 8080, + "description": "ProxyPort is the port to expose the proxy runner on", + "format": "int32", + "maximum": 65535, + "minimum": 1, + "type": "integer" + }, + "rateLimiting": { + "description": "RateLimiting defines rate limiting configuration for the MCP server.\nRequires Redis session storage to be configured for distributed rate limiting.", + "properties": { + "perUser": { + "description": "PerUser is a token bucket applied independently to each authenticated user\nat the server level. Requires authentication to be enabled.\nEach unique userID creates Redis keys that expire after 2x refillPeriod.\nMemory formula: unique_users_per_TTL_window * (1 + num_tools_with_per_user_limits) keys.", + "properties": { + "maxTokens": { + "description": "MaxTokens is the maximum number of tokens (bucket capacity).\nThis is also the burst size: the maximum number of requests that can be served\ninstantaneously before the bucket is depleted.", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "refillPeriod": { + "description": "RefillPeriod is the duration to fully refill the bucket from zero to maxTokens.\nThe effective refill rate is maxTokens / refillPeriod tokens per second.\nFormat: Go duration string (e.g., \"1m0s\", \"30s\", \"1h0m0s\").", + "type": "string" + } + }, + "required": [ + "maxTokens", + "refillPeriod" + ], + "type": "object", + "additionalProperties": false + }, + "shared": { + "description": "Shared is a token bucket shared across all users for the entire server.", + "properties": { + "maxTokens": { + "description": "MaxTokens is the maximum number of tokens (bucket capacity).\nThis is also the burst size: the maximum number of requests that can be served\ninstantaneously before the bucket is depleted.", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "refillPeriod": { + "description": "RefillPeriod is the duration to fully refill the bucket from zero to maxTokens.\nThe effective refill rate is maxTokens / refillPeriod tokens per second.\nFormat: Go duration string (e.g., \"1m0s\", \"30s\", \"1h0m0s\").", + "type": "string" + } + }, + "required": [ + "maxTokens", + "refillPeriod" + ], + "type": "object", + "additionalProperties": false + }, + "tools": { + "description": "Tools defines per-tool rate limit overrides.\nEach entry applies additional rate limits to calls targeting a specific tool name.\nA request must pass both the server-level limit and the per-tool limit.", + "items": { + "description": "ToolRateLimitConfig defines rate limits for a specific tool.\nAt least one of shared or perUser must be configured.", + "properties": { + "name": { + "description": "Name is the MCP tool name this limit applies to.", + "minLength": 1, + "type": "string" + }, + "perUser": { + "description": "PerUser token bucket configuration for this tool.", + "properties": { + "maxTokens": { + "description": "MaxTokens is the maximum number of tokens (bucket capacity).\nThis is also the burst size: the maximum number of requests that can be served\ninstantaneously before the bucket is depleted.", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "refillPeriod": { + "description": "RefillPeriod is the duration to fully refill the bucket from zero to maxTokens.\nThe effective refill rate is maxTokens / refillPeriod tokens per second.\nFormat: Go duration string (e.g., \"1m0s\", \"30s\", \"1h0m0s\").", + "type": "string" + } + }, + "required": [ + "maxTokens", + "refillPeriod" + ], + "type": "object", + "additionalProperties": false + }, + "shared": { + "description": "Shared token bucket for this specific tool.", + "properties": { + "maxTokens": { + "description": "MaxTokens is the maximum number of tokens (bucket capacity).\nThis is also the burst size: the maximum number of requests that can be served\ninstantaneously before the bucket is depleted.", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "refillPeriod": { + "description": "RefillPeriod is the duration to fully refill the bucket from zero to maxTokens.\nThe effective refill rate is maxTokens / refillPeriod tokens per second.\nFormat: Go duration string (e.g., \"1m0s\", \"30s\", \"1h0m0s\").", + "type": "string" + } + }, + "required": [ + "maxTokens", + "refillPeriod" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "name" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "at least one of shared or perUser must be configured", + "rule": "has(self.shared) || has(self.perUser)" + } + ], + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "type": "object", + "x-kubernetes-validations": [ + { + "message": "at least one of shared, perUser, or tools must be configured", + "rule": "has(self.shared) || has(self.perUser) || (has(self.tools) && size(self.tools) > 0)" + } + ], + "additionalProperties": false + }, + "replicas": { + "description": "Replicas is the desired number of proxy runner (thv run) pod replicas.\nMCPServer creates two separate Deployments: one for the proxy runner and one\nfor the MCP server backend. This field controls the proxy runner Deployment.\nWhen nil, the operator does not set Deployment.Spec.Replicas, leaving replica\nmanagement to an HPA or other external controller.", + "format": "int32", + "minimum": 0, + "type": "integer" + }, + "resourceOverrides": { + "description": "ResourceOverrides allows overriding annotations and labels for resources created by the operator", + "properties": { + "proxyDeployment": { + "description": "ProxyDeployment defines overrides for the Proxy Deployment resource (toolhive proxy)", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "env": { + "description": "Env are environment variables to set in the proxy container (thv run process)\nThese affect the toolhive proxy itself, not the MCP server it manages\nUse TOOLHIVE_DEBUG=true to enable debug logging in the proxy", + "items": { + "description": "EnvVar represents an environment variable in a container", + "properties": { + "name": { + "description": "Name of the environment variable", + "type": "string" + }, + "value": { + "description": "Value of the environment variable", + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + }, + "imagePullSecrets": { + "description": "ImagePullSecrets allows specifying image pull secrets for the proxy runner\nThese are applied to both the Deployment and the ServiceAccount", + "items": { + "description": "LocalObjectReference contains enough information to let you locate the\nreferenced object inside the same namespace.", + "properties": { + "name": { + "default": "", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + } + }, + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + }, + "podTemplateMetadataOverrides": { + "description": "ResourceMetadataOverrides defines metadata overrides for a resource", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "proxyService": { + "description": "ProxyService defines overrides for the Proxy Service resource (points to the proxy deployment)", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "resources": { + "description": "Resources defines the resource requirements for the MCP server container", + "properties": { + "limits": { + "description": "Limits describes the maximum amount of compute resources allowed", + "properties": { + "cpu": { + "description": "CPU is the CPU limit in cores (e.g., \"500m\" for 0.5 cores)", + "type": "string" + }, + "memory": { + "description": "Memory is the memory limit in bytes (e.g., \"64Mi\" for 64 megabytes)", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "requests": { + "description": "Requests describes the minimum amount of compute resources required", + "properties": { + "cpu": { + "description": "CPU is the CPU limit in cores (e.g., \"500m\" for 0.5 cores)", + "type": "string" + }, + "memory": { + "description": "Memory is the memory limit in bytes (e.g., \"64Mi\" for 64 megabytes)", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "secrets": { + "description": "Secrets are references to secrets to mount in the MCP server container", + "items": { + "description": "SecretRef is a reference to a secret", + "properties": { + "key": { + "description": "Key is the key in the secret itself", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + }, + "targetEnvName": { + "description": "TargetEnvName is the environment variable to be used when setting up the secret in the MCP server\nIf left unspecified, it defaults to the key", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + }, + "serviceAccount": { + "description": "ServiceAccount is the name of an already existing service account to use by the MCP server.\nIf not specified, a ServiceAccount will be created automatically and used by the MCP server.", + "type": "string" + }, + "sessionAffinity": { + "default": "ClientIP", + "description": "SessionAffinity controls whether the Service routes repeated client connections to the same pod.\nMCP protocols (SSE, streamable-http) are stateful, so ClientIP is the default.\nSet to \"None\" for stateless servers or when using an external load balancer with its own affinity.", + "enum": [ + "ClientIP", + "None" + ], + "type": "string" + }, + "sessionStorage": { + "description": "SessionStorage configures session storage for stateful horizontal scaling.\nWhen nil, no session storage is configured.", + "properties": { + "address": { + "description": "Address is the Redis server address (required when provider is redis)", + "minLength": 1, + "type": "string" + }, + "db": { + "default": 0, + "description": "DB is the Redis database number", + "format": "int32", + "minimum": 0, + "type": "integer" + }, + "keyPrefix": { + "description": "KeyPrefix is an optional prefix for all Redis keys used by ToolHive", + "type": "string" + }, + "passwordRef": { + "description": "PasswordRef is a reference to a Secret key containing the Redis password", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "provider": { + "description": "Provider is the session storage backend type", + "enum": [ + "memory", + "redis" + ], + "type": "string" + } + }, + "required": [ + "provider" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "address is required", + "rule": "self.provider == 'redis' ? has(self.address) : true" + } + ], + "additionalProperties": false + }, + "telemetryConfigRef": { + "description": "TelemetryConfigRef references an MCPTelemetryConfig resource for shared telemetry configuration.\nThe referenced MCPTelemetryConfig must exist in the same namespace as this MCPServer.\nCross-namespace references are not supported for security and isolation reasons.", + "properties": { + "name": { + "description": "Name is the name of the MCPTelemetryConfig resource", + "minLength": 1, + "type": "string" + }, + "serviceName": { + "description": "ServiceName overrides the telemetry service name for this specific server.\nThis MUST be unique per server for proper observability (e.g., distinguishing\ntraces and metrics from different servers sharing the same collector).\nIf empty, defaults to the server name with \"thv-\" prefix at runtime.", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "toolConfigRef": { + "description": "ToolConfigRef references a MCPToolConfig resource for tool filtering and renaming.\nThe referenced MCPToolConfig must exist in the same namespace as this MCPServer.\nCross-namespace references are not supported for security and isolation reasons.", + "properties": { + "name": { + "description": "Name is the name of the MCPToolConfig resource in the same namespace", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "transport": { + "default": "stdio", + "description": "Transport is the transport method for the MCP server (stdio, streamable-http or sse)", + "enum": [ + "stdio", + "streamable-http", + "sse" + ], + "type": "string" + }, + "trustProxyHeaders": { + "default": false, + "description": "TrustProxyHeaders indicates whether to trust X-Forwarded-* headers from reverse proxies\nWhen enabled, the proxy will use X-Forwarded-Proto, X-Forwarded-Host, X-Forwarded-Port,\nand X-Forwarded-Prefix headers to construct endpoint URLs", + "type": "boolean" + }, + "volumes": { + "description": "Volumes are volumes to mount in the MCP server container", + "items": { + "description": "Volume represents a volume to mount in a container", + "properties": { + "hostPath": { + "description": "HostPath is the path on the host to mount", + "type": "string" + }, + "mountPath": { + "description": "MountPath is the path in the container to mount to", + "type": "string" + }, + "name": { + "description": "Name is the name of the volume", + "type": "string" + }, + "readOnly": { + "default": false, + "description": "ReadOnly specifies whether the volume should be mounted read-only", + "type": "boolean" + } + }, + "required": [ + "hostPath", + "mountPath", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + }, + "webhookConfigRef": { + "description": "WebhookConfigRef references a MCPWebhookConfig resource for webhook middleware configuration.\nThe referenced MCPWebhookConfig must exist in the same namespace as this MCPServer.", + "properties": { + "name": { + "description": "Name is the name of the MCPWebhookConfig resource", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "image" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "rateLimiting requires sessionStorage with provider 'redis'", + "rule": "!has(self.rateLimiting) || (has(self.sessionStorage) && self.sessionStorage.provider == 'redis')" + }, + { + "message": "rateLimiting.perUser requires authentication (oidcConfigRef or externalAuthConfigRef)", + "rule": "!(has(self.rateLimiting) && has(self.rateLimiting.perUser)) || has(self.oidcConfigRef) || has(self.externalAuthConfigRef)" + }, + { + "message": "per-tool perUser rate limiting requires authentication (oidcConfigRef or externalAuthConfigRef)", + "rule": "!has(self.rateLimiting) || !has(self.rateLimiting.tools) || self.rateLimiting.tools.all(t, !has(t.perUser)) || has(self.oidcConfigRef) || has(self.externalAuthConfigRef)" + } + ], + "additionalProperties": false + }, + "status": { + "description": "MCPServerStatus defines the observed state of MCPServer", + "properties": { + "authServerConfigHash": { + "description": "AuthServerConfigHash is the hash of the referenced authServerRef spec,\nused to detect configuration changes and trigger reconciliation.", + "type": "string" + }, + "conditions": { + "description": "Conditions represent the latest available observations of the MCPServer's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "externalAuthConfigHash": { + "description": "ExternalAuthConfigHash is the hash of the referenced MCPExternalAuthConfig spec", + "type": "string" + }, + "message": { + "description": "Message provides additional information about the current phase", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration reflects the generation most recently observed by the controller", + "format": "int64", + "type": "integer" + }, + "oidcConfigHash": { + "description": "OIDCConfigHash is the hash of the referenced MCPOIDCConfig spec for change detection", + "type": "string" + }, + "phase": { + "description": "Phase is the current phase of the MCPServer", + "enum": [ + "Pending", + "Ready", + "Failed", + "Terminating", + "Stopped" + ], + "type": "string" + }, + "readyReplicas": { + "description": "ReadyReplicas is the number of ready proxy replicas", + "format": "int32", + "type": "integer" + }, + "telemetryConfigHash": { + "description": "TelemetryConfigHash is the hash of the referenced MCPTelemetryConfig spec for change detection", + "type": "string" + }, + "toolConfigHash": { + "description": "ToolConfigHash stores the hash of the referenced ToolConfig for change detection", + "type": "string" + }, + "url": { + "description": "URL is the URL where the MCP server can be accessed", + "type": "string" + }, + "webhookConfigHash": { + "description": "WebhookConfigHash is the hash of the referenced MCPWebhookConfig spec", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/mcpserver_v1beta1.json b/crdSchemas/mcpserver_v1beta1.json new file mode 100644 index 0000000..76781b3 --- /dev/null +++ b/crdSchemas/mcpserver_v1beta1.json @@ -0,0 +1,946 @@ +{ + "description": "MCPServer is the Schema for the mcpservers API", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPServerSpec defines the desired state of MCPServer", + "properties": { + "args": { + "description": "Args are additional arguments to pass to the MCP server", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "audit": { + "description": "Audit defines audit logging configuration for the MCP server", + "properties": { + "enabled": { + "default": false, + "description": "Enabled controls whether audit logging is enabled\nWhen true, enables audit logging with default configuration", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "authServerRef": { + "description": "AuthServerRef optionally references a resource that configures an embedded\nOAuth 2.0/OIDC authorization server to authenticate MCP clients.\nCurrently the only supported kind is MCPExternalAuthConfig (type: embeddedAuthServer).", + "properties": { + "kind": { + "default": "MCPExternalAuthConfig", + "description": "Kind identifies the type of the referenced resource.", + "enum": [ + "MCPExternalAuthConfig" + ], + "type": "string" + }, + "name": { + "description": "Name is the name of the referenced resource in the same namespace.", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "authzConfig": { + "description": "AuthzConfig defines authorization policy configuration for the MCP server", + "properties": { + "configMap": { + "description": "ConfigMap references a ConfigMap containing authorization configuration\nOnly used when Type is \"configMap\"", + "properties": { + "key": { + "default": "authz.json", + "description": "Key is the key in the ConfigMap that contains the authorization configuration", + "type": "string" + }, + "name": { + "description": "Name is the name of the ConfigMap", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "groupClaimName": { + "description": "GroupClaimName is the JWT claim key that contains group membership for the\nprincipal. When set, takes priority over the well-known defaults\n(\"groups\", \"roles\", \"cognito:groups\"). Use this for IDPs that place\ngroups under a URI-style claim (e.g. \"https://example.com/groups\"). When\nType is \"configMap\", a group_claim_name entry in the referenced ConfigMap\nis overridden by this field if both are set.", + "maxLength": 253, + "type": "string" + }, + "groupEntityType": { + "description": "GroupEntityType is the Cedar entity type name used for principal parent\nUIDs synthesised from JWT group/role claims. Defaults to \"THVGroup\" when\nempty. Must match the entity type used in the static entity store for\ntransitive `in` checks (e.g. `ClaimGroup \u2192 PlatformRole`) to resolve.\nNamespaced names (`Foo::Bar`) are not yet supported. When Type is\n\"configMap\", a group_entity_type entry in the referenced ConfigMap is\noverridden by this field if both are set.", + "maxLength": 63, + "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", + "type": "string" + }, + "inline": { + "description": "Inline contains direct authorization configuration\nOnly used when Type is \"inline\"", + "properties": { + "entitiesJson": { + "default": "[]", + "description": "EntitiesJSON is a JSON string representing Cedar entities. Required when\ntransitive policies (e.g. `ClaimGroup \u2192 PlatformRole`) need a static\nentity store; defaults to \"[]\".", + "type": "string" + }, + "policies": { + "description": "Policies is a list of Cedar policy strings", + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "primaryUpstreamProvider": { + "description": "PrimaryUpstreamProvider names the upstream IDP whose access token's\nclaims Cedar should evaluate.\n\nDeprecated: on VirtualMCPServer this field has moved to\nspec.authServerConfig.primaryUpstreamProvider. The old location is\nstill read for one release for backward compatibility; the\nVirtualMCPServer controller emits an AuthzPrimaryUpstreamProviderDeprecated\nWarning event whenever it is consumed, and removal is planned for the\nrelease after the deprecation cycle.\n\nOn MCPServer and MCPRemoteProxy this field has always been a structural\nno-op (those CRDs do not run an embedded auth server). Setting it\ncontinues to surface the AuthzPrimaryUpstreamProviderIgnored advisory\ncondition; the deprecation does not change that behaviour.", + "maxLength": 63, + "minLength": 1, + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "type": "string" + } + }, + "required": [ + "policies" + ], + "type": "object", + "additionalProperties": false + }, + "roleClaimName": { + "description": "RoleClaimName is the JWT claim key that contains role membership for the\nprincipal. When set, the claim is extracted separately from GroupClaimName\nand both are mapped to the configured GroupEntityType. When Type is\n\"configMap\", a role_claim_name entry in the referenced ConfigMap is\noverridden by this field if both are set.", + "maxLength": 253, + "type": "string" + }, + "type": { + "default": "configMap", + "description": "Type is the type of authorization configuration", + "enum": [ + "configMap", + "inline" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "configMap must be set when type is 'configMap', and must not be set otherwise", + "rule": "self.type == 'configMap' ? has(self.configMap) : !has(self.configMap)" + }, + { + "message": "inline must be set when type is 'inline', and must not be set otherwise", + "rule": "self.type == 'inline' ? has(self.inline) : !has(self.inline)" + } + ], + "additionalProperties": false + }, + "backendReplicas": { + "description": "BackendReplicas is the desired number of MCP server backend pod replicas.\nThis controls the backend Deployment (the MCP server container itself),\nindependent of the proxy runner controlled by Replicas.\nWhen nil, the operator does not set Deployment.Spec.Replicas, leaving replica\nmanagement to an HPA or other external controller.", + "format": "int32", + "minimum": 0, + "type": "integer" + }, + "endpointPrefix": { + "description": "EndpointPrefix is the path prefix to prepend to SSE endpoint URLs.\nThis is used to handle path-based ingress routing scenarios where the ingress\nstrips a path prefix before forwarding to the backend.", + "type": "string" + }, + "env": { + "description": "Env are environment variables to set in the MCP server container", + "items": { + "description": "EnvVar represents an environment variable in a container", + "properties": { + "name": { + "description": "Name of the environment variable", + "type": "string" + }, + "value": { + "description": "Value of the environment variable", + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + }, + "externalAuthConfigRef": { + "description": "ExternalAuthConfigRef references a MCPExternalAuthConfig resource for external authentication.\nThe referenced MCPExternalAuthConfig must exist in the same namespace as this MCPServer.", + "properties": { + "name": { + "description": "Name is the name of the MCPExternalAuthConfig resource", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "groupRef": { + "description": "GroupRef references the MCPGroup this server belongs to.\nThe referenced MCPGroup must be in the same namespace.", + "properties": { + "name": { + "description": "Name is the name of the MCPGroup resource in the same namespace", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "image": { + "description": "Image is the container image for the MCP server", + "type": "string" + }, + "mcpPort": { + "description": "MCPPort is the port that MCP server listens to", + "format": "int32", + "maximum": 65535, + "minimum": 1, + "type": "integer" + }, + "oidcConfigRef": { + "description": "OIDCConfigRef references a shared MCPOIDCConfig resource for OIDC authentication.\nThe referenced MCPOIDCConfig must exist in the same namespace as this MCPServer.\nPer-server overrides (audience, scopes) are specified here; shared provider config\nlives in the MCPOIDCConfig resource.", + "properties": { + "audience": { + "description": "Audience is the expected audience for token validation.\nThis MUST be unique per server to prevent token replay attacks.", + "minLength": 1, + "type": "string" + }, + "name": { + "description": "Name is the name of the MCPOIDCConfig resource", + "minLength": 1, + "type": "string" + }, + "resourceUrl": { + "description": "ResourceURL is the public URL for OAuth protected resource metadata (RFC 9728).\nWhen the server is exposed via Ingress or gateway, set this to the external\nURL that MCP clients connect to. If not specified, defaults to the internal\nKubernetes service URL.", + "type": "string" + }, + "scopes": { + "description": "Scopes is the list of OAuth scopes to advertise in the well-known endpoint (RFC 9728).\nIf empty, defaults to [\"openid\"].", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "required": [ + "audience", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "permissionProfile": { + "description": "PermissionProfile defines the permission profile to use", + "properties": { + "key": { + "description": "Key is the key in the ConfigMap that contains the permission profile\nOnly used when Type is \"configmap\"", + "type": "string" + }, + "name": { + "description": "Name is the name of the permission profile\nIf Type is \"builtin\", Name must be one of: \"none\", \"network\"\nIf Type is \"configmap\", Name is the name of the ConfigMap", + "type": "string" + }, + "type": { + "default": "builtin", + "description": "Type is the type of permission profile reference", + "enum": [ + "builtin", + "configmap" + ], + "type": "string" + } + }, + "required": [ + "name", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "podTemplateSpec": { + "description": "PodTemplateSpec defines the pod template to use for the MCP server\nThis allows for customizing the pod configuration beyond what is provided by the other fields.\nNote that to modify the specific container the MCP server runs in, you must specify\nthe `mcp` container name in the PodTemplateSpec.\nThis field accepts a PodTemplateSpec object as JSON/YAML.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "proxyMode": { + "default": "streamable-http", + "description": "ProxyMode is the proxy mode for stdio transport (sse or streamable-http)\nThis setting is ONLY applicable when Transport is \"stdio\".\nFor direct transports (sse, streamable-http), this field is ignored.\nThe default value is applied by Kubernetes but will be ignored for non-stdio transports.", + "enum": [ + "sse", + "streamable-http" + ], + "type": "string" + }, + "proxyPort": { + "default": 8080, + "description": "ProxyPort is the port to expose the proxy runner on", + "format": "int32", + "maximum": 65535, + "minimum": 1, + "type": "integer" + }, + "rateLimiting": { + "description": "RateLimiting defines rate limiting configuration for the MCP server.\nRequires Redis session storage to be configured for distributed rate limiting.", + "properties": { + "perUser": { + "description": "PerUser is a token bucket applied independently to each authenticated user\nat the server level. Requires authentication to be enabled.\nEach unique userID creates Redis keys that expire after 2x refillPeriod.\nMemory formula: unique_users_per_TTL_window * (1 + num_tools_with_per_user_limits) keys.", + "properties": { + "maxTokens": { + "description": "MaxTokens is the maximum number of tokens (bucket capacity).\nThis is also the burst size: the maximum number of requests that can be served\ninstantaneously before the bucket is depleted.", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "refillPeriod": { + "description": "RefillPeriod is the duration to fully refill the bucket from zero to maxTokens.\nThe effective refill rate is maxTokens / refillPeriod tokens per second.\nFormat: Go duration string (e.g., \"1m0s\", \"30s\", \"1h0m0s\").", + "type": "string" + } + }, + "required": [ + "maxTokens", + "refillPeriod" + ], + "type": "object", + "additionalProperties": false + }, + "shared": { + "description": "Shared is a token bucket shared across all users for the entire server.", + "properties": { + "maxTokens": { + "description": "MaxTokens is the maximum number of tokens (bucket capacity).\nThis is also the burst size: the maximum number of requests that can be served\ninstantaneously before the bucket is depleted.", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "refillPeriod": { + "description": "RefillPeriod is the duration to fully refill the bucket from zero to maxTokens.\nThe effective refill rate is maxTokens / refillPeriod tokens per second.\nFormat: Go duration string (e.g., \"1m0s\", \"30s\", \"1h0m0s\").", + "type": "string" + } + }, + "required": [ + "maxTokens", + "refillPeriod" + ], + "type": "object", + "additionalProperties": false + }, + "tools": { + "description": "Tools defines per-tool rate limit overrides.\nEach entry applies additional rate limits to calls targeting a specific tool name.\nA request must pass both the server-level limit and the per-tool limit.", + "items": { + "description": "ToolRateLimitConfig defines rate limits for a specific tool.\nAt least one of shared or perUser must be configured.", + "properties": { + "name": { + "description": "Name is the MCP tool name this limit applies to.", + "minLength": 1, + "type": "string" + }, + "perUser": { + "description": "PerUser token bucket configuration for this tool.", + "properties": { + "maxTokens": { + "description": "MaxTokens is the maximum number of tokens (bucket capacity).\nThis is also the burst size: the maximum number of requests that can be served\ninstantaneously before the bucket is depleted.", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "refillPeriod": { + "description": "RefillPeriod is the duration to fully refill the bucket from zero to maxTokens.\nThe effective refill rate is maxTokens / refillPeriod tokens per second.\nFormat: Go duration string (e.g., \"1m0s\", \"30s\", \"1h0m0s\").", + "type": "string" + } + }, + "required": [ + "maxTokens", + "refillPeriod" + ], + "type": "object", + "additionalProperties": false + }, + "shared": { + "description": "Shared token bucket for this specific tool.", + "properties": { + "maxTokens": { + "description": "MaxTokens is the maximum number of tokens (bucket capacity).\nThis is also the burst size: the maximum number of requests that can be served\ninstantaneously before the bucket is depleted.", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "refillPeriod": { + "description": "RefillPeriod is the duration to fully refill the bucket from zero to maxTokens.\nThe effective refill rate is maxTokens / refillPeriod tokens per second.\nFormat: Go duration string (e.g., \"1m0s\", \"30s\", \"1h0m0s\").", + "type": "string" + } + }, + "required": [ + "maxTokens", + "refillPeriod" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "name" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "at least one of shared or perUser must be configured", + "rule": "has(self.shared) || has(self.perUser)" + } + ], + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "type": "object", + "x-kubernetes-validations": [ + { + "message": "at least one of shared, perUser, or tools must be configured", + "rule": "has(self.shared) || has(self.perUser) || (has(self.tools) && size(self.tools) > 0)" + } + ], + "additionalProperties": false + }, + "replicas": { + "description": "Replicas is the desired number of proxy runner (thv run) pod replicas.\nMCPServer creates two separate Deployments: one for the proxy runner and one\nfor the MCP server backend. This field controls the proxy runner Deployment.\nWhen nil, the operator does not set Deployment.Spec.Replicas, leaving replica\nmanagement to an HPA or other external controller.", + "format": "int32", + "minimum": 0, + "type": "integer" + }, + "resourceOverrides": { + "description": "ResourceOverrides allows overriding annotations and labels for resources created by the operator", + "properties": { + "proxyDeployment": { + "description": "ProxyDeployment defines overrides for the Proxy Deployment resource (toolhive proxy)", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "env": { + "description": "Env are environment variables to set in the proxy container (thv run process)\nThese affect the toolhive proxy itself, not the MCP server it manages\nUse TOOLHIVE_DEBUG=true to enable debug logging in the proxy", + "items": { + "description": "EnvVar represents an environment variable in a container", + "properties": { + "name": { + "description": "Name of the environment variable", + "type": "string" + }, + "value": { + "description": "Value of the environment variable", + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + }, + "imagePullSecrets": { + "description": "ImagePullSecrets allows specifying image pull secrets for the proxy runner\nThese are applied to both the Deployment and the ServiceAccount", + "items": { + "description": "LocalObjectReference contains enough information to let you locate the\nreferenced object inside the same namespace.", + "properties": { + "name": { + "default": "", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + } + }, + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + }, + "podTemplateMetadataOverrides": { + "description": "ResourceMetadataOverrides defines metadata overrides for a resource", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "proxyService": { + "description": "ProxyService defines overrides for the Proxy Service resource (points to the proxy deployment)", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to add or override on the resource", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to add or override on the resource", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "resources": { + "description": "Resources defines the resource requirements for the MCP server container", + "properties": { + "limits": { + "description": "Limits describes the maximum amount of compute resources allowed", + "properties": { + "cpu": { + "description": "CPU is the CPU limit in cores (e.g., \"500m\" for 0.5 cores)", + "type": "string" + }, + "memory": { + "description": "Memory is the memory limit in bytes (e.g., \"64Mi\" for 64 megabytes)", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "requests": { + "description": "Requests describes the minimum amount of compute resources required", + "properties": { + "cpu": { + "description": "CPU is the CPU limit in cores (e.g., \"500m\" for 0.5 cores)", + "type": "string" + }, + "memory": { + "description": "Memory is the memory limit in bytes (e.g., \"64Mi\" for 64 megabytes)", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "secrets": { + "description": "Secrets are references to secrets to mount in the MCP server container", + "items": { + "description": "SecretRef is a reference to a secret", + "properties": { + "key": { + "description": "Key is the key in the secret itself", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + }, + "targetEnvName": { + "description": "TargetEnvName is the environment variable to be used when setting up the secret in the MCP server\nIf left unspecified, it defaults to the key", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + }, + "serviceAccount": { + "description": "ServiceAccount is the name of an already existing service account to use by the MCP server.\nIf not specified, a ServiceAccount will be created automatically and used by the MCP server.", + "type": "string" + }, + "sessionAffinity": { + "default": "ClientIP", + "description": "SessionAffinity controls whether the Service routes repeated client connections to the same pod.\nMCP protocols (SSE, streamable-http) are stateful, so ClientIP is the default.\nSet to \"None\" for stateless servers or when using an external load balancer with its own affinity.", + "enum": [ + "ClientIP", + "None" + ], + "type": "string" + }, + "sessionStorage": { + "description": "SessionStorage configures session storage for stateful horizontal scaling.\nWhen nil, no session storage is configured.", + "properties": { + "address": { + "description": "Address is the Redis server address (required when provider is redis)", + "minLength": 1, + "type": "string" + }, + "db": { + "default": 0, + "description": "DB is the Redis database number", + "format": "int32", + "minimum": 0, + "type": "integer" + }, + "keyPrefix": { + "description": "KeyPrefix is an optional prefix for all Redis keys used by ToolHive", + "type": "string" + }, + "passwordRef": { + "description": "PasswordRef is a reference to a Secret key containing the Redis password", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "provider": { + "description": "Provider is the session storage backend type", + "enum": [ + "memory", + "redis" + ], + "type": "string" + } + }, + "required": [ + "provider" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "address is required", + "rule": "self.provider == 'redis' ? has(self.address) : true" + } + ], + "additionalProperties": false + }, + "telemetryConfigRef": { + "description": "TelemetryConfigRef references an MCPTelemetryConfig resource for shared telemetry configuration.\nThe referenced MCPTelemetryConfig must exist in the same namespace as this MCPServer.\nCross-namespace references are not supported for security and isolation reasons.", + "properties": { + "name": { + "description": "Name is the name of the MCPTelemetryConfig resource", + "minLength": 1, + "type": "string" + }, + "serviceName": { + "description": "ServiceName overrides the telemetry service name for this specific server.\nThis MUST be unique per server for proper observability (e.g., distinguishing\ntraces and metrics from different servers sharing the same collector).\nIf empty, defaults to the server name with \"thv-\" prefix at runtime.", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "toolConfigRef": { + "description": "ToolConfigRef references a MCPToolConfig resource for tool filtering and renaming.\nThe referenced MCPToolConfig must exist in the same namespace as this MCPServer.\nCross-namespace references are not supported for security and isolation reasons.", + "properties": { + "name": { + "description": "Name is the name of the MCPToolConfig resource in the same namespace", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "transport": { + "default": "stdio", + "description": "Transport is the transport method for the MCP server (stdio, streamable-http or sse)", + "enum": [ + "stdio", + "streamable-http", + "sse" + ], + "type": "string" + }, + "trustProxyHeaders": { + "default": false, + "description": "TrustProxyHeaders indicates whether to trust X-Forwarded-* headers from reverse proxies\nWhen enabled, the proxy will use X-Forwarded-Proto, X-Forwarded-Host, X-Forwarded-Port,\nand X-Forwarded-Prefix headers to construct endpoint URLs", + "type": "boolean" + }, + "volumes": { + "description": "Volumes are volumes to mount in the MCP server container", + "items": { + "description": "Volume represents a volume to mount in a container", + "properties": { + "hostPath": { + "description": "HostPath is the path on the host to mount", + "type": "string" + }, + "mountPath": { + "description": "MountPath is the path in the container to mount to", + "type": "string" + }, + "name": { + "description": "Name is the name of the volume", + "type": "string" + }, + "readOnly": { + "default": false, + "description": "ReadOnly specifies whether the volume should be mounted read-only", + "type": "boolean" + } + }, + "required": [ + "hostPath", + "mountPath", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + }, + "webhookConfigRef": { + "description": "WebhookConfigRef references a MCPWebhookConfig resource for webhook middleware configuration.\nThe referenced MCPWebhookConfig must exist in the same namespace as this MCPServer.", + "properties": { + "name": { + "description": "Name is the name of the MCPWebhookConfig resource", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "image" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "rateLimiting requires sessionStorage with provider 'redis'", + "rule": "!has(self.rateLimiting) || (has(self.sessionStorage) && self.sessionStorage.provider == 'redis')" + }, + { + "message": "rateLimiting.perUser requires authentication (oidcConfigRef or externalAuthConfigRef)", + "rule": "!(has(self.rateLimiting) && has(self.rateLimiting.perUser)) || has(self.oidcConfigRef) || has(self.externalAuthConfigRef)" + }, + { + "message": "per-tool perUser rate limiting requires authentication (oidcConfigRef or externalAuthConfigRef)", + "rule": "!has(self.rateLimiting) || !has(self.rateLimiting.tools) || self.rateLimiting.tools.all(t, !has(t.perUser)) || has(self.oidcConfigRef) || has(self.externalAuthConfigRef)" + } + ], + "additionalProperties": false + }, + "status": { + "description": "MCPServerStatus defines the observed state of MCPServer", + "properties": { + "authServerConfigHash": { + "description": "AuthServerConfigHash is the hash of the referenced authServerRef spec,\nused to detect configuration changes and trigger reconciliation.", + "type": "string" + }, + "conditions": { + "description": "Conditions represent the latest available observations of the MCPServer's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "externalAuthConfigHash": { + "description": "ExternalAuthConfigHash is the hash of the referenced MCPExternalAuthConfig spec", + "type": "string" + }, + "message": { + "description": "Message provides additional information about the current phase", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration reflects the generation most recently observed by the controller", + "format": "int64", + "type": "integer" + }, + "oidcConfigHash": { + "description": "OIDCConfigHash is the hash of the referenced MCPOIDCConfig spec for change detection", + "type": "string" + }, + "phase": { + "description": "Phase is the current phase of the MCPServer", + "enum": [ + "Pending", + "Ready", + "Failed", + "Terminating", + "Stopped" + ], + "type": "string" + }, + "readyReplicas": { + "description": "ReadyReplicas is the number of ready proxy replicas", + "format": "int32", + "type": "integer" + }, + "telemetryConfigHash": { + "description": "TelemetryConfigHash is the hash of the referenced MCPTelemetryConfig spec for change detection", + "type": "string" + }, + "toolConfigHash": { + "description": "ToolConfigHash stores the hash of the referenced ToolConfig for change detection", + "type": "string" + }, + "url": { + "description": "URL is the URL where the MCP server can be accessed", + "type": "string" + }, + "webhookConfigHash": { + "description": "WebhookConfigHash is the hash of the referenced MCPWebhookConfig spec", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/mcpserverentry_v1alpha1.json b/crdSchemas/mcpserverentry_v1alpha1.json new file mode 100644 index 0000000..3e31b1c --- /dev/null +++ b/crdSchemas/mcpserverentry_v1alpha1.json @@ -0,0 +1,242 @@ +{ + "description": "MCPServerEntry is the deprecated v1alpha1 version of the MCPServerEntry resource.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPServerEntrySpec defines the desired state of MCPServerEntry.\nMCPServerEntry is a zero-infrastructure catalog entry that declares a remote MCP\nserver endpoint. Unlike MCPRemoteProxy, it creates no pods, services, or deployments.", + "properties": { + "caBundleRef": { + "description": "CABundleRef references a ConfigMap containing CA certificates for TLS verification\nwhen connecting to the remote MCP server.", + "properties": { + "configMapRef": { + "description": "ConfigMapRef references a ConfigMap containing the CA certificate bundle.\nIf Key is not specified, it defaults to \"ca.crt\".", + "properties": { + "key": { + "description": "The key to select.", + "type": "string" + }, + "name": { + "default": "", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + }, + "optional": { + "description": "Specify whether the ConfigMap or its key must be defined", + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "externalAuthConfigRef": { + "description": "ExternalAuthConfigRef references a MCPExternalAuthConfig resource for token exchange\nwhen connecting to the remote MCP server. The referenced MCPExternalAuthConfig must\nexist in the same namespace as this MCPServerEntry.", + "properties": { + "name": { + "description": "Name is the name of the MCPExternalAuthConfig resource", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "groupRef": { + "description": "GroupRef references the MCPGroup this entry belongs to.\nRequired \u2014 every MCPServerEntry must be part of a group for vMCP discovery.", + "properties": { + "name": { + "description": "Name is the name of the MCPGroup resource in the same namespace", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "headerForward": { + "description": "HeaderForward configures headers to inject into requests to the remote MCP server.\nUse this to add custom headers like API keys or correlation IDs.", + "properties": { + "addHeadersFromSecret": { + "description": "AddHeadersFromSecret references Kubernetes Secrets for sensitive header values.", + "items": { + "description": "HeaderFromSecret defines a header whose value comes from a Kubernetes Secret.", + "properties": { + "headerName": { + "description": "HeaderName is the HTTP header name (e.g., \"X-API-Key\")", + "maxLength": 255, + "minLength": 1, + "type": "string" + }, + "valueSecretRef": { + "description": "ValueSecretRef references the Secret and key containing the header value", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "headerName", + "valueSecretRef" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "headerName" + ], + "x-kubernetes-list-type": "map" + }, + "addPlaintextHeaders": { + "additionalProperties": { + "type": "string" + }, + "description": "AddPlaintextHeaders is a map of header names to literal values to inject into requests.\nWARNING: Values are stored in plaintext and visible via kubectl commands.\nUse addHeadersFromSecret for sensitive data like API keys or tokens.", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + }, + "remoteUrl": { + "description": "RemoteURL is the URL of the remote MCP server.\nBoth HTTP and HTTPS schemes are accepted at admission time.", + "pattern": "^https?://", + "type": "string" + }, + "transport": { + "description": "Transport is the transport method for the remote server (sse or streamable-http).\nNo default is set (unlike MCPRemoteProxy) because MCPServerEntry points at external\nservers the user doesn't control \u2014 requiring explicit transport avoids silent mismatches.", + "enum": [ + "sse", + "streamable-http" + ], + "type": "string" + } + }, + "required": [ + "groupRef", + "remoteUrl", + "transport" + ], + "type": "object", + "additionalProperties": false + }, + "status": { + "description": "MCPServerEntryStatus defines the observed state of MCPServerEntry.", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations of the MCPServerEntry's state.", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "observedGeneration": { + "description": "ObservedGeneration reflects the generation most recently observed by the controller.", + "format": "int64", + "type": "integer" + }, + "phase": { + "default": "Pending", + "description": "Phase indicates the current lifecycle phase of the MCPServerEntry.", + "enum": [ + "Valid", + "Pending", + "Failed" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/mcpserverentry_v1beta1.json b/crdSchemas/mcpserverentry_v1beta1.json new file mode 100644 index 0000000..a2420df --- /dev/null +++ b/crdSchemas/mcpserverentry_v1beta1.json @@ -0,0 +1,242 @@ +{ + "description": "MCPServerEntry is the Schema for the mcpserverentries API.\nIt declares a remote MCP server endpoint for vMCP discovery and routing\nwithout deploying any infrastructure.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPServerEntrySpec defines the desired state of MCPServerEntry.\nMCPServerEntry is a zero-infrastructure catalog entry that declares a remote MCP\nserver endpoint. Unlike MCPRemoteProxy, it creates no pods, services, or deployments.", + "properties": { + "caBundleRef": { + "description": "CABundleRef references a ConfigMap containing CA certificates for TLS verification\nwhen connecting to the remote MCP server.", + "properties": { + "configMapRef": { + "description": "ConfigMapRef references a ConfigMap containing the CA certificate bundle.\nIf Key is not specified, it defaults to \"ca.crt\".", + "properties": { + "key": { + "description": "The key to select.", + "type": "string" + }, + "name": { + "default": "", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + }, + "optional": { + "description": "Specify whether the ConfigMap or its key must be defined", + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "externalAuthConfigRef": { + "description": "ExternalAuthConfigRef references a MCPExternalAuthConfig resource for token exchange\nwhen connecting to the remote MCP server. The referenced MCPExternalAuthConfig must\nexist in the same namespace as this MCPServerEntry.", + "properties": { + "name": { + "description": "Name is the name of the MCPExternalAuthConfig resource", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "groupRef": { + "description": "GroupRef references the MCPGroup this entry belongs to.\nRequired \u2014 every MCPServerEntry must be part of a group for vMCP discovery.", + "properties": { + "name": { + "description": "Name is the name of the MCPGroup resource in the same namespace", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "headerForward": { + "description": "HeaderForward configures headers to inject into requests to the remote MCP server.\nUse this to add custom headers like API keys or correlation IDs.", + "properties": { + "addHeadersFromSecret": { + "description": "AddHeadersFromSecret references Kubernetes Secrets for sensitive header values.", + "items": { + "description": "HeaderFromSecret defines a header whose value comes from a Kubernetes Secret.", + "properties": { + "headerName": { + "description": "HeaderName is the HTTP header name (e.g., \"X-API-Key\")", + "maxLength": 255, + "minLength": 1, + "type": "string" + }, + "valueSecretRef": { + "description": "ValueSecretRef references the Secret and key containing the header value", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "headerName", + "valueSecretRef" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "headerName" + ], + "x-kubernetes-list-type": "map" + }, + "addPlaintextHeaders": { + "additionalProperties": { + "type": "string" + }, + "description": "AddPlaintextHeaders is a map of header names to literal values to inject into requests.\nWARNING: Values are stored in plaintext and visible via kubectl commands.\nUse addHeadersFromSecret for sensitive data like API keys or tokens.", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + }, + "remoteUrl": { + "description": "RemoteURL is the URL of the remote MCP server.\nBoth HTTP and HTTPS schemes are accepted at admission time.", + "pattern": "^https?://", + "type": "string" + }, + "transport": { + "description": "Transport is the transport method for the remote server (sse or streamable-http).\nNo default is set (unlike MCPRemoteProxy) because MCPServerEntry points at external\nservers the user doesn't control \u2014 requiring explicit transport avoids silent mismatches.", + "enum": [ + "sse", + "streamable-http" + ], + "type": "string" + } + }, + "required": [ + "groupRef", + "remoteUrl", + "transport" + ], + "type": "object", + "additionalProperties": false + }, + "status": { + "description": "MCPServerEntryStatus defines the observed state of MCPServerEntry.", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations of the MCPServerEntry's state.", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "observedGeneration": { + "description": "ObservedGeneration reflects the generation most recently observed by the controller.", + "format": "int64", + "type": "integer" + }, + "phase": { + "default": "Pending", + "description": "Phase indicates the current lifecycle phase of the MCPServerEntry.", + "enum": [ + "Valid", + "Pending", + "Failed" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/mcptelemetryconfig_v1alpha1.json b/crdSchemas/mcptelemetryconfig_v1alpha1.json new file mode 100644 index 0000000..7084b43 --- /dev/null +++ b/crdSchemas/mcptelemetryconfig_v1alpha1.json @@ -0,0 +1,295 @@ +{ + "description": "MCPTelemetryConfig is the deprecated v1alpha1 version of the MCPTelemetryConfig resource.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPTelemetryConfigSpec defines the desired state of MCPTelemetryConfig.\nThe spec uses a nested structure with openTelemetry and prometheus sub-objects\nfor clear separation of concerns.", + "properties": { + "openTelemetry": { + "description": "OpenTelemetry defines OpenTelemetry configuration (OTLP endpoint, tracing, metrics)", + "properties": { + "caBundleRef": { + "description": "CABundleRef references a ConfigMap containing a CA certificate bundle for the OTLP endpoint.\nWhen specified, the operator mounts the ConfigMap into the proxyrunner pod and configures\nthe OTLP exporters to trust the custom CA. This is useful when the OTLP collector uses\nTLS with certificates signed by an internal or private CA.", + "properties": { + "configMapRef": { + "description": "ConfigMapRef references a ConfigMap containing the CA certificate bundle.\nIf Key is not specified, it defaults to \"ca.crt\".", + "properties": { + "key": { + "description": "The key to select.", + "type": "string" + }, + "name": { + "default": "", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + }, + "optional": { + "description": "Specify whether the ConfigMap or its key must be defined", + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "enabled": { + "default": false, + "description": "Enabled controls whether OpenTelemetry is enabled", + "type": "boolean" + }, + "endpoint": { + "description": "Endpoint is the OTLP endpoint URL for tracing and metrics", + "type": "string" + }, + "headers": { + "additionalProperties": { + "type": "string" + }, + "description": "Headers contains authentication headers for the OTLP endpoint.\nFor secret-backed credentials, use sensitiveHeaders instead.", + "type": "object" + }, + "insecure": { + "default": false, + "description": "Insecure indicates whether to use HTTP instead of HTTPS for the OTLP endpoint", + "type": "boolean" + }, + "metrics": { + "description": "Metrics defines OpenTelemetry metrics-specific configuration", + "properties": { + "enabled": { + "default": false, + "description": "Enabled controls whether OTLP metrics are sent", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "resourceAttributes": { + "additionalProperties": { + "type": "string" + }, + "description": "ResourceAttributes contains custom resource attributes to be added to all telemetry signals.\nThese become OTel resource attributes (e.g., deployment.environment, service.namespace).\nNote: service.name is intentionally excluded \u2014 it is set per-server via\nMCPTelemetryConfigReference.ServiceName.", + "type": "object" + }, + "sensitiveHeaders": { + "description": "SensitiveHeaders contains headers whose values are stored in Kubernetes Secrets.\nUse this for credential headers (e.g., API keys, bearer tokens) instead of\nembedding secrets in the headers field.", + "items": { + "description": "SensitiveHeader represents a header whose value is stored in a Kubernetes Secret.\nThis allows credential headers (e.g., API keys, bearer tokens) to be securely\nreferenced without embedding secrets inline in the MCPTelemetryConfig resource.", + "properties": { + "name": { + "description": "Name is the header name (e.g., \"Authorization\", \"X-API-Key\")", + "minLength": 1, + "type": "string" + }, + "secretKeyRef": { + "description": "SecretKeyRef is a reference to a Kubernetes Secret key containing the header value", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "name", + "secretKeyRef" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + }, + "tracing": { + "description": "Tracing defines OpenTelemetry tracing configuration", + "properties": { + "enabled": { + "default": false, + "description": "Enabled controls whether OTLP tracing is sent", + "type": "boolean" + }, + "samplingRate": { + "default": "0.05", + "description": "SamplingRate is the trace sampling rate (0.0-1.0)", + "pattern": "^(0(\\.\\d+)?|1(\\.0+)?)$", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "useLegacyAttributes": { + "default": true, + "description": "UseLegacyAttributes controls whether legacy attribute names are emitted alongside\nthe new MCP OTEL semantic convention names. Defaults to true for backward compatibility.\nThis will change to false in a future release and eventually be removed.", + "type": "boolean" + } + }, + "type": "object", + "x-kubernetes-validations": [ + { + "message": "a header name cannot appear in both headers and sensitiveHeaders", + "rule": "!has(self.headers) || !has(self.sensitiveHeaders) || self.sensitiveHeaders.all(sh, !(sh.name in self.headers))" + } + ], + "additionalProperties": false + }, + "prometheus": { + "description": "Prometheus defines Prometheus-specific configuration", + "properties": { + "enabled": { + "default": false, + "description": "Enabled controls whether Prometheus metrics endpoint is exposed", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "status": { + "description": "MCPTelemetryConfigStatus defines the observed state of MCPTelemetryConfig", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations of the MCPTelemetryConfig's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "configHash": { + "description": "ConfigHash is a hash of the current configuration for change detection", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration is the most recent generation observed for this MCPTelemetryConfig.", + "format": "int64", + "type": "integer" + }, + "referencingWorkloads": { + "description": "ReferencingWorkloads lists workloads that reference this MCPTelemetryConfig", + "items": { + "description": "WorkloadReference identifies a workload that references a shared configuration resource.\nNamespace is implicit \u2014 cross-namespace references are not supported.", + "properties": { + "kind": { + "description": "Kind is the type of workload resource", + "enum": [ + "MCPServer", + "VirtualMCPServer", + "MCPRemoteProxy" + ], + "type": "string" + }, + "name": { + "description": "Name is the name of the workload resource", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/mcptelemetryconfig_v1beta1.json b/crdSchemas/mcptelemetryconfig_v1beta1.json new file mode 100644 index 0000000..0eb14ec --- /dev/null +++ b/crdSchemas/mcptelemetryconfig_v1beta1.json @@ -0,0 +1,295 @@ +{ + "description": "MCPTelemetryConfig is the Schema for the mcptelemetryconfigs API.\nMCPTelemetryConfig resources are namespace-scoped and can only be referenced by\nMCPServer resources within the same namespace. Cross-namespace references\nare not supported for security and isolation reasons.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPTelemetryConfigSpec defines the desired state of MCPTelemetryConfig.\nThe spec uses a nested structure with openTelemetry and prometheus sub-objects\nfor clear separation of concerns.", + "properties": { + "openTelemetry": { + "description": "OpenTelemetry defines OpenTelemetry configuration (OTLP endpoint, tracing, metrics)", + "properties": { + "caBundleRef": { + "description": "CABundleRef references a ConfigMap containing a CA certificate bundle for the OTLP endpoint.\nWhen specified, the operator mounts the ConfigMap into the proxyrunner pod and configures\nthe OTLP exporters to trust the custom CA. This is useful when the OTLP collector uses\nTLS with certificates signed by an internal or private CA.", + "properties": { + "configMapRef": { + "description": "ConfigMapRef references a ConfigMap containing the CA certificate bundle.\nIf Key is not specified, it defaults to \"ca.crt\".", + "properties": { + "key": { + "description": "The key to select.", + "type": "string" + }, + "name": { + "default": "", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + }, + "optional": { + "description": "Specify whether the ConfigMap or its key must be defined", + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "enabled": { + "default": false, + "description": "Enabled controls whether OpenTelemetry is enabled", + "type": "boolean" + }, + "endpoint": { + "description": "Endpoint is the OTLP endpoint URL for tracing and metrics", + "type": "string" + }, + "headers": { + "additionalProperties": { + "type": "string" + }, + "description": "Headers contains authentication headers for the OTLP endpoint.\nFor secret-backed credentials, use sensitiveHeaders instead.", + "type": "object" + }, + "insecure": { + "default": false, + "description": "Insecure indicates whether to use HTTP instead of HTTPS for the OTLP endpoint", + "type": "boolean" + }, + "metrics": { + "description": "Metrics defines OpenTelemetry metrics-specific configuration", + "properties": { + "enabled": { + "default": false, + "description": "Enabled controls whether OTLP metrics are sent", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "resourceAttributes": { + "additionalProperties": { + "type": "string" + }, + "description": "ResourceAttributes contains custom resource attributes to be added to all telemetry signals.\nThese become OTel resource attributes (e.g., deployment.environment, service.namespace).\nNote: service.name is intentionally excluded \u2014 it is set per-server via\nMCPTelemetryConfigReference.ServiceName.", + "type": "object" + }, + "sensitiveHeaders": { + "description": "SensitiveHeaders contains headers whose values are stored in Kubernetes Secrets.\nUse this for credential headers (e.g., API keys, bearer tokens) instead of\nembedding secrets in the headers field.", + "items": { + "description": "SensitiveHeader represents a header whose value is stored in a Kubernetes Secret.\nThis allows credential headers (e.g., API keys, bearer tokens) to be securely\nreferenced without embedding secrets inline in the MCPTelemetryConfig resource.", + "properties": { + "name": { + "description": "Name is the header name (e.g., \"Authorization\", \"X-API-Key\")", + "minLength": 1, + "type": "string" + }, + "secretKeyRef": { + "description": "SecretKeyRef is a reference to a Kubernetes Secret key containing the header value", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "name", + "secretKeyRef" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + }, + "tracing": { + "description": "Tracing defines OpenTelemetry tracing configuration", + "properties": { + "enabled": { + "default": false, + "description": "Enabled controls whether OTLP tracing is sent", + "type": "boolean" + }, + "samplingRate": { + "default": "0.05", + "description": "SamplingRate is the trace sampling rate (0.0-1.0)", + "pattern": "^(0(\\.\\d+)?|1(\\.0+)?)$", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "useLegacyAttributes": { + "default": true, + "description": "UseLegacyAttributes controls whether legacy attribute names are emitted alongside\nthe new MCP OTEL semantic convention names. Defaults to true for backward compatibility.\nThis will change to false in a future release and eventually be removed.", + "type": "boolean" + } + }, + "type": "object", + "x-kubernetes-validations": [ + { + "message": "a header name cannot appear in both headers and sensitiveHeaders", + "rule": "!has(self.headers) || !has(self.sensitiveHeaders) || self.sensitiveHeaders.all(sh, !(sh.name in self.headers))" + } + ], + "additionalProperties": false + }, + "prometheus": { + "description": "Prometheus defines Prometheus-specific configuration", + "properties": { + "enabled": { + "default": false, + "description": "Enabled controls whether Prometheus metrics endpoint is exposed", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "status": { + "description": "MCPTelemetryConfigStatus defines the observed state of MCPTelemetryConfig", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations of the MCPTelemetryConfig's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "configHash": { + "description": "ConfigHash is a hash of the current configuration for change detection", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration is the most recent generation observed for this MCPTelemetryConfig.", + "format": "int64", + "type": "integer" + }, + "referencingWorkloads": { + "description": "ReferencingWorkloads lists workloads that reference this MCPTelemetryConfig", + "items": { + "description": "WorkloadReference identifies a workload that references a shared configuration resource.\nNamespace is implicit \u2014 cross-namespace references are not supported.", + "properties": { + "kind": { + "description": "Kind is the type of workload resource", + "enum": [ + "MCPServer", + "VirtualMCPServer", + "MCPRemoteProxy" + ], + "type": "string" + }, + "name": { + "description": "Name is the name of the workload resource", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/mcptoolconfig_v1alpha1.json b/crdSchemas/mcptoolconfig_v1alpha1.json new file mode 100644 index 0000000..6996292 --- /dev/null +++ b/crdSchemas/mcptoolconfig_v1alpha1.json @@ -0,0 +1,192 @@ +{ + "description": "MCPToolConfig is the deprecated v1alpha1 version of the MCPToolConfig resource.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPToolConfigSpec defines the desired state of MCPToolConfig.\nMCPToolConfig resources are namespace-scoped and can only be referenced by\nMCPServer resources in the same namespace.", + "properties": { + "toolsFilter": { + "description": "ToolsFilter is a list of tool names to filter (allow list).\nOnly tools in this list will be exposed by the MCP server.\nIf empty, all tools are exposed.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "set" + }, + "toolsOverride": { + "additionalProperties": { + "description": "ToolOverride represents a tool override configuration.\nBoth Name and Description can be overridden independently, but\nthey can't be both empty.", + "properties": { + "annotations": { + "description": "Annotations overrides specific tool annotation fields.\nOnly specified fields are overridden; others pass through from the backend.", + "properties": { + "destructiveHint": { + "description": "DestructiveHint overrides the destructive hint annotation.", + "type": "boolean" + }, + "idempotentHint": { + "description": "IdempotentHint overrides the idempotent hint annotation.", + "type": "boolean" + }, + "openWorldHint": { + "description": "OpenWorldHint overrides the open-world hint annotation.", + "type": "boolean" + }, + "readOnlyHint": { + "description": "ReadOnlyHint overrides the read-only hint annotation.", + "type": "boolean" + }, + "title": { + "description": "Title overrides the human-readable title annotation.", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "description": { + "description": "Description is the redefined description of the tool", + "type": "string" + }, + "name": { + "description": "Name is the redefined name of the tool", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "description": "ToolsOverride is a map from actual tool names to their overridden configuration.\nThis allows renaming tools and/or changing their descriptions.", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + }, + "status": { + "description": "MCPToolConfigStatus defines the observed state of MCPToolConfig", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations of the MCPToolConfig's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "configHash": { + "description": "ConfigHash is a hash of the current configuration for change detection", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration is the most recent generation observed for this MCPToolConfig.\nIt corresponds to the MCPToolConfig's generation, which is updated on mutation by the API Server.", + "format": "int64", + "type": "integer" + }, + "referenceCount": { + "description": "ReferenceCount is the number of workloads referencing this config.", + "format": "int32", + "type": "integer" + }, + "referencingWorkloads": { + "description": "ReferencingWorkloads is a list of workload resources that reference this MCPToolConfig.\nEach entry identifies the workload by kind and name.", + "items": { + "description": "WorkloadReference identifies a workload that references a shared configuration resource.\nNamespace is implicit \u2014 cross-namespace references are not supported.", + "properties": { + "kind": { + "description": "Kind is the type of workload resource", + "enum": [ + "MCPServer", + "VirtualMCPServer", + "MCPRemoteProxy" + ], + "type": "string" + }, + "name": { + "description": "Name is the name of the workload resource", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/mcptoolconfig_v1beta1.json b/crdSchemas/mcptoolconfig_v1beta1.json new file mode 100644 index 0000000..3dc278c --- /dev/null +++ b/crdSchemas/mcptoolconfig_v1beta1.json @@ -0,0 +1,192 @@ +{ + "description": "MCPToolConfig is the Schema for the mcptoolconfigs API.\nMCPToolConfig resources are namespace-scoped and can only be referenced by\nMCPServer resources within the same namespace. Cross-namespace references\nare not supported for security and isolation reasons.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPToolConfigSpec defines the desired state of MCPToolConfig.\nMCPToolConfig resources are namespace-scoped and can only be referenced by\nMCPServer resources in the same namespace.", + "properties": { + "toolsFilter": { + "description": "ToolsFilter is a list of tool names to filter (allow list).\nOnly tools in this list will be exposed by the MCP server.\nIf empty, all tools are exposed.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "set" + }, + "toolsOverride": { + "additionalProperties": { + "description": "ToolOverride represents a tool override configuration.\nBoth Name and Description can be overridden independently, but\nthey can't be both empty.", + "properties": { + "annotations": { + "description": "Annotations overrides specific tool annotation fields.\nOnly specified fields are overridden; others pass through from the backend.", + "properties": { + "destructiveHint": { + "description": "DestructiveHint overrides the destructive hint annotation.", + "type": "boolean" + }, + "idempotentHint": { + "description": "IdempotentHint overrides the idempotent hint annotation.", + "type": "boolean" + }, + "openWorldHint": { + "description": "OpenWorldHint overrides the open-world hint annotation.", + "type": "boolean" + }, + "readOnlyHint": { + "description": "ReadOnlyHint overrides the read-only hint annotation.", + "type": "boolean" + }, + "title": { + "description": "Title overrides the human-readable title annotation.", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "description": { + "description": "Description is the redefined description of the tool", + "type": "string" + }, + "name": { + "description": "Name is the redefined name of the tool", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "description": "ToolsOverride is a map from actual tool names to their overridden configuration.\nThis allows renaming tools and/or changing their descriptions.", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + }, + "status": { + "description": "MCPToolConfigStatus defines the observed state of MCPToolConfig", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations of the MCPToolConfig's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "configHash": { + "description": "ConfigHash is a hash of the current configuration for change detection", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration is the most recent generation observed for this MCPToolConfig.\nIt corresponds to the MCPToolConfig's generation, which is updated on mutation by the API Server.", + "format": "int64", + "type": "integer" + }, + "referenceCount": { + "description": "ReferenceCount is the number of workloads referencing this config.", + "format": "int32", + "type": "integer" + }, + "referencingWorkloads": { + "description": "ReferencingWorkloads is a list of workload resources that reference this MCPToolConfig.\nEach entry identifies the workload by kind and name.", + "items": { + "description": "WorkloadReference identifies a workload that references a shared configuration resource.\nNamespace is implicit \u2014 cross-namespace references are not supported.", + "properties": { + "kind": { + "description": "Kind is the type of workload resource", + "enum": [ + "MCPServer", + "VirtualMCPServer", + "MCPRemoteProxy" + ], + "type": "string" + }, + "name": { + "description": "Name is the name of the workload resource", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/mcpwebhookconfig_v1alpha1.json b/crdSchemas/mcpwebhookconfig_v1alpha1.json new file mode 100644 index 0000000..61507c8 --- /dev/null +++ b/crdSchemas/mcpwebhookconfig_v1alpha1.json @@ -0,0 +1,393 @@ +{ + "description": "MCPWebhookConfig is the Schema for the mcpwebhookconfigs API.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "MCPWebhookConfigSpec defines the desired state of MCPWebhookConfig", + "properties": { + "mutating": { + "description": "Mutating webhooks are called to transform MCP requests before processing.", + "items": { + "description": "WebhookSpec defines the configuration for a single webhook middleware", + "properties": { + "failurePolicy": { + "default": "fail", + "description": "FailurePolicy defines how to handle errors when communicating with the webhook.\nSupported values: \"fail\", \"ignore\". Defaults to \"fail\".", + "enum": [ + "fail", + "ignore" + ], + "type": "string" + }, + "hmacSecretRef": { + "description": "HMACSecretRef references a Kubernetes Secret containing the HMAC signing key\nused to sign the webhook payload. If set, the X-Toolhive-Signature header will be injected.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "name": { + "description": "Name is a unique identifier for this webhook", + "maxLength": 63, + "minLength": 1, + "type": "string" + }, + "timeout": { + "description": "Timeout configures the maximum time to wait for the webhook to respond.\nDefaults to 10s if not specified. Maximum is 30s.", + "format": "duration", + "type": "string" + }, + "tlsConfig": { + "description": "TLSConfig contains optional TLS configuration for the webhook connection.", + "properties": { + "caSecretRef": { + "description": "CASecretRef references a Secret containing the CA certificate bundle used to verify the webhook server's certificate.\nContains a bundle of PEM-encoded X.509 certificates.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "clientCertSecretRef": { + "description": "ClientCertSecretRef references a Secret containing the client certificate for mTLS authentication.\nThe referenced key must contain a PEM-encoded client certificate.\nUse ClientKeySecretRef to provide the corresponding private key.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "clientKeySecretRef": { + "description": "ClientKeySecretRef references a Secret containing the private key for the client certificate.\nRequired when ClientCertSecretRef is set to enable mTLS.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "insecureSkipVerify": { + "description": "InsecureSkipVerify disables server certificate verification.\nWARNING: This should only be used for development/testing and not in production environments.", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "url": { + "description": "URL is the endpoint to call for this webhook. Must be an HTTP/HTTPS URL.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "name", + "url" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + }, + "validating": { + "description": "Validating webhooks are called to approve or deny MCP requests.", + "items": { + "description": "WebhookSpec defines the configuration for a single webhook middleware", + "properties": { + "failurePolicy": { + "default": "fail", + "description": "FailurePolicy defines how to handle errors when communicating with the webhook.\nSupported values: \"fail\", \"ignore\". Defaults to \"fail\".", + "enum": [ + "fail", + "ignore" + ], + "type": "string" + }, + "hmacSecretRef": { + "description": "HMACSecretRef references a Kubernetes Secret containing the HMAC signing key\nused to sign the webhook payload. If set, the X-Toolhive-Signature header will be injected.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "name": { + "description": "Name is a unique identifier for this webhook", + "maxLength": 63, + "minLength": 1, + "type": "string" + }, + "timeout": { + "description": "Timeout configures the maximum time to wait for the webhook to respond.\nDefaults to 10s if not specified. Maximum is 30s.", + "format": "duration", + "type": "string" + }, + "tlsConfig": { + "description": "TLSConfig contains optional TLS configuration for the webhook connection.", + "properties": { + "caSecretRef": { + "description": "CASecretRef references a Secret containing the CA certificate bundle used to verify the webhook server's certificate.\nContains a bundle of PEM-encoded X.509 certificates.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "clientCertSecretRef": { + "description": "ClientCertSecretRef references a Secret containing the client certificate for mTLS authentication.\nThe referenced key must contain a PEM-encoded client certificate.\nUse ClientKeySecretRef to provide the corresponding private key.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "clientKeySecretRef": { + "description": "ClientKeySecretRef references a Secret containing the private key for the client certificate.\nRequired when ClientCertSecretRef is set to enable mTLS.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "insecureSkipVerify": { + "description": "InsecureSkipVerify disables server certificate verification.\nWARNING: This should only be used for development/testing and not in production environments.", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "url": { + "description": "URL is the endpoint to call for this webhook. Must be an HTTP/HTTPS URL.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "name", + "url" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + } + }, + "type": "object", + "x-kubernetes-validations": [ + { + "message": "at least one validating or mutating webhook must be defined", + "rule": "(has(self.validating) ? size(self.validating) : 0) + (has(self.mutating) ? size(self.mutating) : 0) > 0" + } + ], + "additionalProperties": false + }, + "status": { + "description": "MCPWebhookConfigStatus defines the observed state of MCPWebhookConfig", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "configHash": { + "description": "ConfigHash is a hash of the spec, used for detecting changes", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration is the last observed generation corresponding to the current status", + "format": "int64", + "type": "integer" + }, + "referencingWorkloads": { + "description": "ReferencingWorkloads is a list of workload resources that reference this MCPWebhookConfig.\nEach entry identifies the workload by kind and name.", + "items": { + "description": "WorkloadReference identifies a workload that references a shared configuration resource.\nNamespace is implicit \u2014 cross-namespace references are not supported.", + "properties": { + "kind": { + "description": "Kind is the type of workload resource", + "enum": [ + "MCPServer", + "VirtualMCPServer", + "MCPRemoteProxy" + ], + "type": "string" + }, + "name": { + "description": "Name is the name of the workload resource", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/podlogs_v1alpha2.json b/crdSchemas/podlogs_v1alpha2.json new file mode 100644 index 0000000..36e4446 --- /dev/null +++ b/crdSchemas/podlogs_v1alpha2.json @@ -0,0 +1,183 @@ +{ + "description": "PodLogs defines how to collect logs for a Pod.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "PodLogsSpec defines how to collect logs for a Pod.", + "properties": { + "namespaceSelector": { + "description": "Selector to select which namespaces the Pod objects are discovered from.", + "properties": { + "matchExpressions": { + "description": "matchExpressions is a list of label selector requirements. The requirements are ANDed.", + "items": { + "description": "A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.", + "properties": { + "key": { + "description": "key is the label key that the selector applies to.", + "type": "string" + }, + "operator": { + "description": "operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.", + "type": "string" + }, + "values": { + "description": "values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "description": "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.", + "type": "object" + } + }, + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + }, + "relabelings": { + "description": "RelabelConfigs to apply to logs before delivering.", + "items": { + "description": "RelabelConfig allows dynamic rewriting of the label set, being applied to samples before ingestion. It defines ``-section of Prometheus configuration. More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs", + "properties": { + "action": { + "default": "replace", + "description": "Action to perform based on regex matching. Default is 'replace'. uppercase and lowercase actions require Prometheus >= 2.36.", + "enum": [ + "replace", + "Replace", + "keep", + "Keep", + "drop", + "Drop", + "hashmod", + "HashMod", + "labelmap", + "LabelMap", + "labeldrop", + "LabelDrop", + "labelkeep", + "LabelKeep", + "lowercase", + "Lowercase", + "uppercase", + "Uppercase" + ], + "type": "string" + }, + "modulus": { + "description": "Modulus to take of the hash of the source label values.", + "format": "int64", + "type": "integer" + }, + "regex": { + "description": "Regular expression against which the extracted value is matched. Default is '(.*)'", + "type": "string" + }, + "replacement": { + "description": "Replacement value against which a regex replace is performed if the regular expression matches. Regex capture groups are available. Default is '$1'", + "type": "string" + }, + "separator": { + "description": "Separator placed between concatenated source label values. default is ';'.", + "type": "string" + }, + "sourceLabels": { + "description": "The source labels select values from existing labels. Their content is concatenated using the configured separator and matched against the configured regular expression for the replace, keep, and drop actions.", + "items": { + "description": "LabelName is a valid Prometheus label name which may only contain ASCII letters, numbers, as well as underscores.", + "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$", + "type": "string" + }, + "type": "array" + }, + "targetLabel": { + "description": "Label to which the resulting value is written in a replace action. It is mandatory for replace actions. Regex capture groups are available.", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "type": "array" + }, + "selector": { + "description": "Selector to select Pod objects. Required.", + "properties": { + "matchExpressions": { + "description": "matchExpressions is a list of label selector requirements. The requirements are ANDed.", + "items": { + "description": "A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.", + "properties": { + "key": { + "description": "key is the label key that the selector applies to.", + "type": "string" + }, + "operator": { + "description": "operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.", + "type": "string" + }, + "values": { + "description": "values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "description": "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.", + "type": "object" + } + }, + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + } + }, + "required": [ + "selector" + ], + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/virtualmcpcompositetooldefinition_v1alpha1.json b/crdSchemas/virtualmcpcompositetooldefinition_v1alpha1.json new file mode 100644 index 0000000..f12c087 --- /dev/null +++ b/crdSchemas/virtualmcpcompositetooldefinition_v1alpha1.json @@ -0,0 +1,352 @@ +{ + "description": "VirtualMCPCompositeToolDefinition is the deprecated v1alpha1 version of the VirtualMCPCompositeToolDefinition resource.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "VirtualMCPCompositeToolDefinitionSpec defines the desired state of VirtualMCPCompositeToolDefinition.\nThis embeds the CompositeToolConfig from pkg/vmcp/config to share the configuration model\nbetween CLI and operator usage.", + "properties": { + "description": { + "description": "Description describes what the workflow does.", + "type": "string" + }, + "name": { + "description": "Name is the workflow name (unique identifier).", + "type": "string" + }, + "output": { + "description": "Output defines the structured output schema for this workflow.\nIf not specified, the workflow returns the last step's output (backward compatible).", + "properties": { + "properties": { + "additionalProperties": { + "description": "OutputProperty defines a single output property.\nFor non-object types, Value is required.\nFor object types, either Value or Properties must be specified (but not both).", + "properties": { + "default": { + "description": "Default is the fallback value if template expansion fails.\nType coercion is applied to match the declared Type.", + "x-kubernetes-preserve-unknown-fields": true + }, + "description": { + "description": "Description is a human-readable description exposed to clients and models", + "type": "string" + }, + "properties": { + "description": "Properties defines nested properties for object types.\nEach nested property has full metadata (type, description, value/properties).", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "type": { + "description": "Type is the JSON Schema type: \"string\", \"integer\", \"number\", \"boolean\", \"object\", \"array\"", + "enum": [ + "string", + "integer", + "number", + "boolean", + "object", + "array" + ], + "type": "string" + }, + "value": { + "description": "Value is a template string for constructing the runtime value.\nFor object types, this can be a JSON string that will be deserialized.\nSupports template syntax: {{.steps.step_id.output.field}}, {{.params.param_name}}", + "type": "string" + }, + "additionalProperties": false + }, + "required": [ + "type" + ], + "type": "object", + "additionalProperties": false + }, + "description": "Properties defines the output properties.\nMap key is the property name, value is the property definition.", + "type": "object" + }, + "required": { + "description": "Required lists property names that must be present in the output.", + "items": { + "type": "string" + }, + "type": "array" + }, + "additionalProperties": false + }, + "required": [ + "properties" + ], + "type": "object", + "additionalProperties": false + }, + "parameters": { + "description": "Parameters defines input parameter schema in JSON Schema format.\nShould be a JSON Schema object with \"type\": \"object\" and \"properties\".\nExample:\n {\n \"type\": \"object\",\n \"properties\": {\n \"param1\": {\"type\": \"string\", \"default\": \"value\"},\n \"param2\": {\"type\": \"integer\"}\n },\n \"required\": [\"param2\"]\n }\n\nWe use json.Map rather than a typed struct because JSON Schema is highly\nflexible with many optional fields (default, enum, minimum, maximum, pattern,\nitems, additionalProperties, oneOf, anyOf, allOf, etc.). Using json.Map\nallows full JSON Schema compatibility without needing to define every possible\nfield, and matches how the MCP SDK handles inputSchema.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "steps": { + "description": "Steps are the workflow steps to execute.", + "items": { + "description": "WorkflowStepConfig defines a single workflow step.\nThis matches the proposal's step configuration (lines 180-255).", + "properties": { + "arguments": { + "description": "Arguments is a map of argument values with template expansion support.\nSupports Go template syntax with .params and .steps for string values.\nNon-string values (integers, booleans, arrays, objects) are passed as-is.\nNote: the templating is only supported on the first level of the key-value pairs.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "collection": { + "description": "Collection is a Go template expression that resolves to a JSON array or a slice.\nOnly used when Type is \"forEach\".", + "type": "string" + }, + "condition": { + "description": "Condition is a template expression that determines if the step should execute", + "type": "string" + }, + "defaultResults": { + "description": "DefaultResults provides fallback output values when this step is skipped\n(due to condition evaluating to false) or fails (when onError.action is \"continue\").\nEach key corresponds to an output field name referenced by downstream steps.\nRequired if the step may be skipped AND downstream steps reference this step's output.", + "x-kubernetes-preserve-unknown-fields": true + }, + "dependsOn": { + "description": "DependsOn lists step IDs that must complete before this step", + "items": { + "type": "string" + }, + "type": "array" + }, + "id": { + "description": "ID is the unique identifier for this step.", + "type": "string" + }, + "itemVar": { + "description": "ItemVar is the variable name used to reference the current item in forEach templates.\nDefaults to \"item\" if not specified.\nOnly used when Type is \"forEach\".", + "type": "string" + }, + "maxIterations": { + "description": "MaxIterations limits the number of items that can be iterated over.\nDefaults to 100, hard cap at 1000.\nOnly used when Type is \"forEach\".", + "type": "integer" + }, + "maxParallel": { + "description": "MaxParallel limits the number of concurrent iterations in a forEach step.\nDefaults to the DAG executor's maxParallel (10).\nOnly used when Type is \"forEach\".", + "type": "integer" + }, + "message": { + "description": "Message is the elicitation message\nOnly used when Type is \"elicitation\"", + "type": "string" + }, + "onCancel": { + "description": "OnCancel defines the action to take when the user cancels/dismisses the elicitation\nOnly used when Type is \"elicitation\"", + "properties": { + "action": { + "default": "abort", + "description": "Action defines the action to take when the user declines or cancels\n- skip_remaining: Skip remaining steps in the workflow\n- abort: Abort the entire workflow execution\n- continue: Continue to the next step", + "enum": [ + "skip_remaining", + "abort", + "continue" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "onDecline": { + "description": "OnDecline defines the action to take when the user explicitly declines the elicitation\nOnly used when Type is \"elicitation\"", + "properties": { + "action": { + "default": "abort", + "description": "Action defines the action to take when the user declines or cancels\n- skip_remaining: Skip remaining steps in the workflow\n- abort: Abort the entire workflow execution\n- continue: Continue to the next step", + "enum": [ + "skip_remaining", + "abort", + "continue" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "onError": { + "description": "OnError defines error handling behavior", + "properties": { + "action": { + "default": "abort", + "description": "Action defines the action to take on error", + "enum": [ + "abort", + "continue", + "retry" + ], + "type": "string" + }, + "retryCount": { + "description": "RetryCount is the maximum number of retries\nOnly used when Action is \"retry\"", + "type": "integer" + }, + "retryDelay": { + "description": "RetryDelay is the delay between retry attempts\nOnly used when Action is \"retry\"", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "schema": { + "description": "Schema defines the expected response schema for elicitation", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "step": { + "description": "InnerStep defines the step to execute for each item in the collection.\nOnly used when Type is \"forEach\". Only tool-type inner steps are supported.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "timeout": { + "description": "Timeout is the maximum execution time for this step", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "tool": { + "description": "Tool is the tool to call (format: \"workload.tool_name\")\nOnly used when Type is \"tool\"", + "type": "string" + }, + "type": { + "default": "tool", + "description": "Type is the step type (tool, elicitation, etc.)", + "enum": [ + "tool", + "elicitation", + "forEach" + ], + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + }, + "timeout": { + "description": "Timeout is the maximum workflow execution time.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + } + }, + "required": [ + "name", + "steps" + ], + "type": "object", + "additionalProperties": false + }, + "status": { + "description": "VirtualMCPCompositeToolDefinitionStatus defines the observed state of VirtualMCPCompositeToolDefinition", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations of the workflow's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "observedGeneration": { + "description": "ObservedGeneration is the most recent generation observed for this VirtualMCPCompositeToolDefinition\nIt corresponds to the resource's generation, which is updated on mutation by the API Server", + "format": "int64", + "type": "integer" + }, + "referencingVirtualServers": { + "description": "ReferencingVirtualServers lists VirtualMCPServer resources that reference this workflow\nThis helps track which servers need to be reconciled when this workflow changes", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "set" + }, + "validationErrors": { + "description": "ValidationErrors contains validation error messages if ValidationStatus is Invalid", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "validationStatus": { + "description": "ValidationStatus indicates the validation state of the workflow\n- Valid: Workflow structure is valid\n- Invalid: Workflow has validation errors", + "enum": [ + "Valid", + "Invalid", + "Unknown" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/virtualmcpcompositetooldefinition_v1beta1.json b/crdSchemas/virtualmcpcompositetooldefinition_v1beta1.json new file mode 100644 index 0000000..a7af011 --- /dev/null +++ b/crdSchemas/virtualmcpcompositetooldefinition_v1beta1.json @@ -0,0 +1,352 @@ +{ + "description": "VirtualMCPCompositeToolDefinition is the Schema for the virtualmcpcompositetooldefinitions API\nVirtualMCPCompositeToolDefinition defines reusable composite workflows that can be referenced\nby multiple VirtualMCPServer instances", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "VirtualMCPCompositeToolDefinitionSpec defines the desired state of VirtualMCPCompositeToolDefinition.\nThis embeds the CompositeToolConfig from pkg/vmcp/config to share the configuration model\nbetween CLI and operator usage.", + "properties": { + "description": { + "description": "Description describes what the workflow does.", + "type": "string" + }, + "name": { + "description": "Name is the workflow name (unique identifier).", + "type": "string" + }, + "output": { + "description": "Output defines the structured output schema for this workflow.\nIf not specified, the workflow returns the last step's output (backward compatible).", + "properties": { + "properties": { + "additionalProperties": { + "description": "OutputProperty defines a single output property.\nFor non-object types, Value is required.\nFor object types, either Value or Properties must be specified (but not both).", + "properties": { + "default": { + "description": "Default is the fallback value if template expansion fails.\nType coercion is applied to match the declared Type.", + "x-kubernetes-preserve-unknown-fields": true + }, + "description": { + "description": "Description is a human-readable description exposed to clients and models", + "type": "string" + }, + "properties": { + "description": "Properties defines nested properties for object types.\nEach nested property has full metadata (type, description, value/properties).", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "type": { + "description": "Type is the JSON Schema type: \"string\", \"integer\", \"number\", \"boolean\", \"object\", \"array\"", + "enum": [ + "string", + "integer", + "number", + "boolean", + "object", + "array" + ], + "type": "string" + }, + "value": { + "description": "Value is a template string for constructing the runtime value.\nFor object types, this can be a JSON string that will be deserialized.\nSupports template syntax: {{.steps.step_id.output.field}}, {{.params.param_name}}", + "type": "string" + }, + "additionalProperties": false + }, + "required": [ + "type" + ], + "type": "object", + "additionalProperties": false + }, + "description": "Properties defines the output properties.\nMap key is the property name, value is the property definition.", + "type": "object" + }, + "required": { + "description": "Required lists property names that must be present in the output.", + "items": { + "type": "string" + }, + "type": "array" + }, + "additionalProperties": false + }, + "required": [ + "properties" + ], + "type": "object", + "additionalProperties": false + }, + "parameters": { + "description": "Parameters defines input parameter schema in JSON Schema format.\nShould be a JSON Schema object with \"type\": \"object\" and \"properties\".\nExample:\n {\n \"type\": \"object\",\n \"properties\": {\n \"param1\": {\"type\": \"string\", \"default\": \"value\"},\n \"param2\": {\"type\": \"integer\"}\n },\n \"required\": [\"param2\"]\n }\n\nWe use json.Map rather than a typed struct because JSON Schema is highly\nflexible with many optional fields (default, enum, minimum, maximum, pattern,\nitems, additionalProperties, oneOf, anyOf, allOf, etc.). Using json.Map\nallows full JSON Schema compatibility without needing to define every possible\nfield, and matches how the MCP SDK handles inputSchema.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "steps": { + "description": "Steps are the workflow steps to execute.", + "items": { + "description": "WorkflowStepConfig defines a single workflow step.\nThis matches the proposal's step configuration (lines 180-255).", + "properties": { + "arguments": { + "description": "Arguments is a map of argument values with template expansion support.\nSupports Go template syntax with .params and .steps for string values.\nNon-string values (integers, booleans, arrays, objects) are passed as-is.\nNote: the templating is only supported on the first level of the key-value pairs.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "collection": { + "description": "Collection is a Go template expression that resolves to a JSON array or a slice.\nOnly used when Type is \"forEach\".", + "type": "string" + }, + "condition": { + "description": "Condition is a template expression that determines if the step should execute", + "type": "string" + }, + "defaultResults": { + "description": "DefaultResults provides fallback output values when this step is skipped\n(due to condition evaluating to false) or fails (when onError.action is \"continue\").\nEach key corresponds to an output field name referenced by downstream steps.\nRequired if the step may be skipped AND downstream steps reference this step's output.", + "x-kubernetes-preserve-unknown-fields": true + }, + "dependsOn": { + "description": "DependsOn lists step IDs that must complete before this step", + "items": { + "type": "string" + }, + "type": "array" + }, + "id": { + "description": "ID is the unique identifier for this step.", + "type": "string" + }, + "itemVar": { + "description": "ItemVar is the variable name used to reference the current item in forEach templates.\nDefaults to \"item\" if not specified.\nOnly used when Type is \"forEach\".", + "type": "string" + }, + "maxIterations": { + "description": "MaxIterations limits the number of items that can be iterated over.\nDefaults to 100, hard cap at 1000.\nOnly used when Type is \"forEach\".", + "type": "integer" + }, + "maxParallel": { + "description": "MaxParallel limits the number of concurrent iterations in a forEach step.\nDefaults to the DAG executor's maxParallel (10).\nOnly used when Type is \"forEach\".", + "type": "integer" + }, + "message": { + "description": "Message is the elicitation message\nOnly used when Type is \"elicitation\"", + "type": "string" + }, + "onCancel": { + "description": "OnCancel defines the action to take when the user cancels/dismisses the elicitation\nOnly used when Type is \"elicitation\"", + "properties": { + "action": { + "default": "abort", + "description": "Action defines the action to take when the user declines or cancels\n- skip_remaining: Skip remaining steps in the workflow\n- abort: Abort the entire workflow execution\n- continue: Continue to the next step", + "enum": [ + "skip_remaining", + "abort", + "continue" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "onDecline": { + "description": "OnDecline defines the action to take when the user explicitly declines the elicitation\nOnly used when Type is \"elicitation\"", + "properties": { + "action": { + "default": "abort", + "description": "Action defines the action to take when the user declines or cancels\n- skip_remaining: Skip remaining steps in the workflow\n- abort: Abort the entire workflow execution\n- continue: Continue to the next step", + "enum": [ + "skip_remaining", + "abort", + "continue" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "onError": { + "description": "OnError defines error handling behavior", + "properties": { + "action": { + "default": "abort", + "description": "Action defines the action to take on error", + "enum": [ + "abort", + "continue", + "retry" + ], + "type": "string" + }, + "retryCount": { + "description": "RetryCount is the maximum number of retries\nOnly used when Action is \"retry\"", + "type": "integer" + }, + "retryDelay": { + "description": "RetryDelay is the delay between retry attempts\nOnly used when Action is \"retry\"", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "schema": { + "description": "Schema defines the expected response schema for elicitation", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "step": { + "description": "InnerStep defines the step to execute for each item in the collection.\nOnly used when Type is \"forEach\". Only tool-type inner steps are supported.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "timeout": { + "description": "Timeout is the maximum execution time for this step", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "tool": { + "description": "Tool is the tool to call (format: \"workload.tool_name\")\nOnly used when Type is \"tool\"", + "type": "string" + }, + "type": { + "default": "tool", + "description": "Type is the step type (tool, elicitation, etc.)", + "enum": [ + "tool", + "elicitation", + "forEach" + ], + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + }, + "timeout": { + "description": "Timeout is the maximum workflow execution time.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + } + }, + "required": [ + "name", + "steps" + ], + "type": "object", + "additionalProperties": false + }, + "status": { + "description": "VirtualMCPCompositeToolDefinitionStatus defines the observed state of VirtualMCPCompositeToolDefinition", + "properties": { + "conditions": { + "description": "Conditions represent the latest available observations of the workflow's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "observedGeneration": { + "description": "ObservedGeneration is the most recent generation observed for this VirtualMCPCompositeToolDefinition\nIt corresponds to the resource's generation, which is updated on mutation by the API Server", + "format": "int64", + "type": "integer" + }, + "referencingVirtualServers": { + "description": "ReferencingVirtualServers lists VirtualMCPServer resources that reference this workflow\nThis helps track which servers need to be reconciled when this workflow changes", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "set" + }, + "validationErrors": { + "description": "ValidationErrors contains validation error messages if ValidationStatus is Invalid", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "validationStatus": { + "description": "ValidationStatus indicates the validation state of the workflow\n- Valid: Workflow structure is valid\n- Invalid: Workflow has validation errors", + "enum": [ + "Valid", + "Invalid", + "Unknown" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/virtualmcpserver_v1alpha1.json b/crdSchemas/virtualmcpserver_v1alpha1.json new file mode 100644 index 0000000..b11eefb --- /dev/null +++ b/crdSchemas/virtualmcpserver_v1alpha1.json @@ -0,0 +1,2694 @@ +{ + "description": "VirtualMCPServer is the deprecated v1alpha1 version of the VirtualMCPServer resource.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "VirtualMCPServerSpec defines the desired state of VirtualMCPServer", + "properties": { + "authServerConfig": { + "description": "AuthServerConfig configures an embedded OAuth authorization server.\nWhen set, the vMCP server acts as an OIDC issuer, drives users through\nupstream IDPs, and issues ToolHive JWTs. The embedded AS becomes the\nIncomingAuth OIDC provider \u2014 its issuer must match IncomingAuth.OIDCConfigRef\nso that tokens it issues are accepted by the vMCP's incoming auth middleware.\nWhen nil, IncomingAuth uses an external IDP and behavior is unchanged.", + "properties": { + "authorizationEndpointBaseUrl": { + "description": "AuthorizationEndpointBaseURL overrides the base URL used for the authorization_endpoint\nin the OAuth discovery document. When set, the discovery document will advertise\n`{authorizationEndpointBaseUrl}/oauth/authorize` instead of `{issuer}/oauth/authorize`.\nAll other endpoints (token, registration, JWKS) remain derived from the issuer.\nThis is useful when the browser-facing authorization endpoint needs to be on a\ndifferent host than the issuer used for backend-to-backend calls.\nMust be a valid HTTPS URL (or HTTP for localhost) without query, fragment, or trailing slash.", + "pattern": "^https?://[^\\s?#]+[^/\\s?#]$", + "type": "string" + }, + "baselineClientScopes": { + "description": "BaselineClientScopes is a baseline set of OAuth 2.0 scopes guaranteed to be\nincluded in every client registration. The embedded auth server unions these\nscopes into the registered set returned by RFC 7591 Dynamic Client\nRegistration, so a client that narrows the `scope` field at /oauth/register\ncan still request the baseline scopes at /oauth/authorize. All values must\nbe present in the upstream-derived scopesSupported set; the auth server\nfails to start if any value is missing.\n\nSecurity: every client registered via /oauth/register will gain the\nability to request these scopes at /oauth/authorize, regardless of what\nthe client itself requested. Keep the baseline narrow (typically\n\"openid\" and \"offline_access\"). Adding a privileged scope here \u2014 e.g.\n\"admin:read\" \u2014 would grant it to every DCR-registered client, including\npublic clients like Claude Code, Cursor, and VS Code.", + "items": { + "minLength": 1, + "pattern": "^[\\x21\\x23-\\x5B\\x5D-\\x7E]+$", + "type": "string" + }, + "maxItems": 10, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "hmacSecretRefs": { + "description": "HMACSecretRefs references Kubernetes Secrets containing symmetric secrets for signing\nauthorization codes and refresh tokens (opaque tokens).\nCurrent secret must be at least 32 bytes and cryptographically random.\nSupports secret rotation via multiple entries (first is current, rest are for verification).\nIf not specified, an ephemeral secret will be auto-generated (development only -\nauth codes and refresh tokens will be invalid after restart).", + "items": { + "description": "SecretKeyRef is a reference to a key within a Secret", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "issuer": { + "description": "Issuer is the issuer identifier for this authorization server.\nThis will be included in the \"iss\" claim of issued tokens.\nMust be a valid HTTPS URL (or HTTP for localhost) without query, fragment, or trailing slash (per RFC 8414).", + "pattern": "^https?://[^\\s?#]+[^/\\s?#]$", + "type": "string" + }, + "primaryUpstreamProvider": { + "description": "PrimaryUpstreamProvider names the upstream IDP whose access token Cedar\nshould read claims from when authorising a request. Must match the name\nof one of the entries in UpstreamProviders. When empty, the controller\nauto-selects the first entry of UpstreamProviders.\n\nOnly meaningful on VirtualMCPServer, where multiple upstream providers\ncan be configured and Cedar needs to pick which token's claims to\nevaluate. The VirtualMCPServer controller validates this field against\nUpstreamProviders at admission and rejects unresolvable values.\n\nOn MCPServer and MCPRemoteProxy this field is structurally present (the\nEmbeddedAuthServerConfig struct is shared) but has no runtime effect:\nthose CRDs are restricted to a single upstream so there is no choice to\nmake. Setting it on those CRDs is silently ignored.", + "maxLength": 63, + "minLength": 1, + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "type": "string" + }, + "signingKeySecretRefs": { + "description": "SigningKeySecretRefs references Kubernetes Secrets containing signing keys for JWT operations.\nSupports key rotation by allowing multiple keys (oldest keys are used for verification only).\nIf not specified, an ephemeral signing key will be auto-generated (development only -\nJWTs will be invalid after restart).", + "items": { + "description": "SecretKeyRef is a reference to a key within a Secret", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "maxItems": 5, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "storage": { + "description": "Storage configures the storage backend for the embedded auth server.\nIf not specified, defaults to in-memory storage.", + "properties": { + "redis": { + "description": "Redis configures the Redis storage backend.\nRequired when type is \"redis\".", + "properties": { + "aclUserConfig": { + "description": "ACLUserConfig configures Redis ACL user authentication.", + "properties": { + "passwordSecretRef": { + "description": "PasswordSecretRef references a Secret containing the Redis ACL password.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "usernameSecretRef": { + "description": "UsernameSecretRef references a Secret containing the Redis ACL username.\nWhen omitted, connections use legacy password-only AUTH. Omit for managed\nRedis tiers that do not support ACL users (e.g. GCP Memorystore Basic/Standard\nHA, Azure Cache for Redis). Set for services that support ACL users (e.g. AWS\nElastiCache non-cluster with Redis 6+ RBAC).", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "passwordSecretRef" + ], + "type": "object", + "additionalProperties": false + }, + "addr": { + "description": "Addr is the Redis server address (host:port). Required for standalone and cluster modes.\nUse for managed Redis services that expose a single endpoint (GCP Memorystore basic tier,\nAWS ElastiCache without cluster mode, or cluster-mode services when clusterMode is true).\nMutually exclusive with sentinelConfig.", + "type": "string" + }, + "clusterMode": { + "description": "ClusterMode enables the Redis Cluster protocol. Set to true when addr points to a\nRedis Cluster discovery endpoint (e.g., GCP Memorystore Cluster, AWS ElastiCache\ncluster mode enabled). Requires addr to be set.", + "type": "boolean" + }, + "dialTimeout": { + "default": "5s", + "description": "DialTimeout is the timeout for establishing connections.\nFormat: Go duration string (e.g., \"5s\", \"1m\").", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "readTimeout": { + "default": "3s", + "description": "ReadTimeout is the timeout for socket reads.\nFormat: Go duration string (e.g., \"3s\", \"1m\").", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "sentinelConfig": { + "description": "SentinelConfig holds Redis Sentinel configuration.\nUse for self-managed Redis with Sentinel-based HA. Mutually exclusive with addr.", + "properties": { + "db": { + "default": 0, + "description": "DB is the Redis database number.", + "format": "int32", + "type": "integer" + }, + "masterName": { + "description": "MasterName is the name of the Redis master monitored by Sentinel.", + "type": "string" + }, + "sentinelAddrs": { + "description": "SentinelAddrs is a list of Sentinel host:port addresses.\nMutually exclusive with SentinelService.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "sentinelService": { + "description": "SentinelService enables automatic discovery from a Kubernetes Service.\nMutually exclusive with SentinelAddrs.", + "properties": { + "name": { + "description": "Name of the Sentinel Service.", + "type": "string" + }, + "namespace": { + "description": "Namespace of the Sentinel Service (defaults to same namespace).", + "type": "string" + }, + "port": { + "default": 26379, + "description": "Port of the Sentinel service.", + "format": "int32", + "type": "integer" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "masterName" + ], + "type": "object", + "additionalProperties": false + }, + "sentinelTls": { + "description": "SentinelTLS configures TLS for connections to Sentinel instances.\nOnly applies when sentinelConfig is set. Presence of this field enables TLS.", + "properties": { + "caCertSecretRef": { + "description": "CACertSecretRef references a Secret containing a PEM-encoded CA certificate\nfor verifying the server. When not specified, system root CAs are used.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "insecureSkipVerify": { + "description": "InsecureSkipVerify skips TLS certificate verification.\nUse when connecting to services with self-signed certificates.", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "tls": { + "description": "TLS configures TLS for connections to the Redis/Valkey master.\nPresence of this field enables TLS. Omit to use plaintext.", + "properties": { + "caCertSecretRef": { + "description": "CACertSecretRef references a Secret containing a PEM-encoded CA certificate\nfor verifying the server. When not specified, system root CAs are used.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "insecureSkipVerify": { + "description": "InsecureSkipVerify skips TLS certificate verification.\nUse when connecting to services with self-signed certificates.", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "writeTimeout": { + "default": "3s", + "description": "WriteTimeout is the timeout for socket writes.\nFormat: Go duration string (e.g., \"3s\", \"1m\").", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + } + }, + "required": [ + "aclUserConfig" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "exactly one of addr or sentinelConfig must be set", + "rule": "(has(self.addr) && self.addr.size() > 0) != has(self.sentinelConfig)" + }, + { + "message": "clusterMode requires addr to be set", + "rule": "!(has(self.clusterMode) && self.clusterMode) || (has(self.addr) && self.addr.size() > 0)" + } + ], + "additionalProperties": false + }, + "type": { + "default": "memory", + "description": "Type specifies the storage backend type.\nValid values: \"memory\" (default), \"redis\".", + "enum": [ + "memory", + "redis" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "tokenLifespans": { + "description": "TokenLifespans configures the duration that various tokens are valid.\nIf not specified, defaults are applied (access: 1h, refresh: 7d, authCode: 10m).", + "properties": { + "accessTokenLifespan": { + "description": "AccessTokenLifespan is the duration that access tokens are valid.\nFormat: Go duration string (e.g., \"1h\", \"30m\", \"24h\").\nIf empty, defaults to 1 hour.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "authCodeLifespan": { + "description": "AuthCodeLifespan is the duration that authorization codes are valid.\nFormat: Go duration string (e.g., \"10m\", \"5m\").\nIf empty, defaults to 10 minutes.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "refreshTokenLifespan": { + "description": "RefreshTokenLifespan is the duration that refresh tokens are valid.\nFormat: Go duration string (e.g., \"168h\", \"7d\" as \"168h\").\nIf empty, defaults to 7 days (168h).", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "upstreamProviders": { + "description": "UpstreamProviders configures connections to upstream Identity Providers.\nThe embedded auth server delegates authentication to these providers.\nMCPServer and MCPRemoteProxy support a single upstream; VirtualMCPServer supports multiple.", + "items": { + "description": "UpstreamProviderConfig defines configuration for an upstream Identity Provider.\n\nExactly one of OIDCConfig or OAuth2Config must be set and must match the\ndeclared Type: oidc-typed providers set OIDCConfig, oauth2-typed providers\nset OAuth2Config. The CEL rule below enforces the pairing at admission; the\nmatching Go-level check in validateUpstreamProvider provides defense-in-depth\nfor stored objects.\n\nThe rule is structured as a chain of equality checks ending in an explicit\n`false`, so adding a new UpstreamProviderType value without extending this\nrule fails admission instead of silently demanding the OAuth2 shape. When\nadding a new type, extend both this rule and validateUpstreamProvider.", + "properties": { + "name": { + "description": "Name uniquely identifies this upstream provider.\nUsed for routing decisions and session binding in multi-upstream scenarios.\nMust be lowercase alphanumeric with hyphens (DNS-label-like).", + "maxLength": 63, + "minLength": 1, + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "type": "string" + }, + "oauth2Config": { + "description": "OAuth2Config contains OAuth 2.0-specific configuration.\nRequired when Type is \"oauth2\", must be nil when Type is \"oidc\".", + "properties": { + "additionalAuthorizationParams": { + "additionalProperties": { + "type": "string" + }, + "description": "AdditionalAuthorizationParams are extra query parameters to include in\nauthorization requests sent to the upstream provider.\nThis is useful for providers that require custom parameters, such as\nGoogle's access_type=offline for obtaining refresh tokens.\nFramework-managed parameters (response_type, client_id, redirect_uri,\nscope, state, code_challenge, code_challenge_method, nonce) are not allowed.", + "maxProperties": 16, + "type": "object" + }, + "authorizationEndpoint": { + "description": "AuthorizationEndpoint is the URL for the OAuth authorization endpoint.", + "pattern": "^https?://.*$", + "type": "string" + }, + "clientId": { + "description": "ClientID is the OAuth 2.0 client identifier registered with the upstream IDP.\nMutually exclusive with DCRConfig: when DCRConfig is set, ClientID is obtained\nat runtime via RFC 7591 Dynamic Client Registration and must be left empty.", + "type": "string" + }, + "clientSecretRef": { + "description": "ClientSecretRef references a Kubernetes Secret containing the OAuth 2.0 client secret.\nOptional for public clients using PKCE instead of client secret.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "dcrConfig": { + "description": "DCRConfig enables RFC 7591 Dynamic Client Registration against the upstream\nauthorization server. When set, the client credentials are obtained at\nruntime rather than being pre-provisioned, and ClientID must be left empty.\nMutually exclusive with ClientID.", + "properties": { + "discoveryUrl": { + "description": "DiscoveryURL is the RFC 8414 / OIDC Discovery document URL. The resolver\nissues a single GET against this URL (no well-known-path fallback) and\nreads registration_endpoint, authorization_endpoint, token_endpoint,\ntoken_endpoint_auth_methods_supported, and scopes_supported from the\nresponse.\nMutually exclusive with RegistrationEndpoint.\nHTTPS is required because the registration endpoint resolved from this\ndocument carries the initial access token and the issued client_secret\n(RFC 7591 \u00a73, RFC 8414 \u00a73). MaxLength is a defensive size cap (etcd\nobject budget, regex evaluation cost) and matches the conventional URL\nlength cap.", + "maxLength": 2048, + "pattern": "^https://[^\\s?#]+[^/\\s?#]$", + "type": "string" + }, + "initialAccessTokenRef": { + "description": "InitialAccessTokenRef is an optional reference to a Kubernetes Secret\ncarrying an RFC 7591 \u00a73 initial access token. When set, the resolver\npresents the token value as a Bearer credential on the registration\nrequest. Mirrors the ClientSecretRef pattern.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "registrationEndpoint": { + "description": "RegistrationEndpoint is the RFC 7591 registration endpoint URL used\ndirectly, bypassing discovery. When using this field, the caller is\nexpected to also supply AuthorizationEndpoint, TokenEndpoint, and an\nexplicit Scopes list on the parent OAuth2UpstreamConfig.\nMutually exclusive with DiscoveryURL.\nHTTPS is required because the registration endpoint carries the initial\naccess token and the issued client_secret (RFC 7591 \u00a73, RFC 8414 \u00a73).\nMaxLength is a defensive size cap (etcd object budget, regex evaluation\ncost) and matches the conventional URL length cap.", + "maxLength": 2048, + "pattern": "^https://[^\\s?#]+[^/\\s?#]$", + "type": "string" + }, + "softwareId": { + "description": "SoftwareID is the RFC 7591 \"software_id\" registration metadata value,\nidentifying the client software independent of any particular\nregistration instance. Typically a UUID or short identifier.", + "maxLength": 255, + "type": "string" + }, + "softwareStatement": { + "description": "SoftwareStatement is the RFC 7591 \"software_statement\" JWT asserting\nmetadata about the client software, signed by a party the authorization\nserver trusts.\n\nStored inline on the CR. The JWT is signed but not encrypted, so its\ncontents are visible to anyone with get/list/watch on this resource and\nappear in etcd backups in plaintext. Treat the value as non-confidential\n(signed attestation, not a secret). Operators that rotate software\nstatements like bearer credentials should keep them at the authorization\nserver side and rely on the registration endpoint's initial access\ntoken (see InitialAccessTokenRef) instead of placing them on the CR.\n\nBounded to 16384 characters as a defensive size cap (etcd object\nbudget, regex evaluation cost). Real-world signed statements with\nembedded x5c certificate chains, JWKS keys, or OIDC-Federation\ntrust-framework metadata routinely exceed 4 KB.", + "maxLength": 16384, + "type": "string" + } + }, + "type": "object", + "x-kubernetes-validations": [ + { + "message": "exactly one of discoveryUrl or registrationEndpoint must be set", + "rule": "has(self.discoveryUrl) != has(self.registrationEndpoint)" + } + ], + "additionalProperties": false + }, + "identityFromToken": { + "description": "IdentityFromToken extracts user identity (subject, name, email) directly\nfrom the OAuth2 token-endpoint response body using gjson dot-notation paths.\nWhen set, the embedded auth server skips the userinfo HTTP call entirely\nand resolves identity from the token response. See IdentityFromTokenConfig\nfor trust-model and uniqueness considerations.", + "properties": { + "emailPath": { + "description": "EmailPath is the dot-notation path to the email address field in the token response.\nIf not specified or if the path does not resolve to a string, the email is omitted.\nOmit the field entirely rather than setting it to an empty string.", + "maxLength": 256, + "minLength": 1, + "type": "string" + }, + "namePath": { + "description": "NamePath is the dot-notation path to the display name field in the token response.\nIf not specified or if the path does not resolve to a string, the display name is omitted.\nOmit the field entirely rather than setting it to an empty string.", + "maxLength": 256, + "minLength": 1, + "type": "string" + }, + "subjectPath": { + "description": "SubjectPath is the dot-notation path to the subject (user ID) field in the token response.\nWarning: claims read from the token response are trusted only via TLS, not\ncryptographically verified; prefer OIDC ID tokens when verifiable claims are required.\nExample: \"authed_user.id\" for Slack (top-level token-response field). For providers\nwhose token response embeds the access token as a JWT (e.g. Snowflake), use the\n\"@upstreamjwt\" modifier to decode the payload, e.g. \"access_token|@upstreamjwt|sub\".\nThe \"@upstreamjwt\" modifier performs no signature verification either.", + "maxLength": 256, + "minLength": 1, + "type": "string" + } + }, + "required": [ + "subjectPath" + ], + "type": "object", + "additionalProperties": false + }, + "redirectUri": { + "description": "RedirectURI is the callback URL where the upstream IDP will redirect after authentication.\nWhen not specified, defaults to `{resourceUrl}/oauth/callback` where `resourceUrl` is the\nURL associated with the resource (e.g., MCPServer or vMCP) using this config.", + "type": "string" + }, + "scopes": { + "description": "Scopes are the OAuth scopes to request from the upstream IDP.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "tokenEndpoint": { + "description": "TokenEndpoint is the URL for the OAuth token endpoint.", + "pattern": "^https?://.*$", + "type": "string" + }, + "tokenResponseMapping": { + "description": "TokenResponseMapping configures custom field extraction from non-standard token responses.\nSome OAuth providers (e.g., GovSlack) nest token fields under non-standard paths\ninstead of returning them at the top level. When set, ToolHive performs the token\nexchange HTTP call directly and extracts fields using the configured dot-notation paths.\nIf nil, standard OAuth 2.0 token response parsing is used.\nFor extracting user identity from the token response, see IdentityFromToken.", + "properties": { + "accessTokenPath": { + "description": "AccessTokenPath is the dot-notation path to the access token in the response.\nExample: \"authed_user.access_token\"", + "minLength": 1, + "type": "string" + }, + "expiresInPath": { + "description": "ExpiresInPath is the dot-notation path to the expires_in value (in seconds).\nIf not specified, defaults to \"expires_in\".", + "type": "string" + }, + "refreshTokenPath": { + "description": "RefreshTokenPath is the dot-notation path to the refresh token in the response.\nIf not specified, defaults to \"refresh_token\".", + "type": "string" + }, + "scopePath": { + "description": "ScopePath is the dot-notation path to the scope string in the response.\nIf not specified, defaults to \"scope\".", + "type": "string" + } + }, + "required": [ + "accessTokenPath" + ], + "type": "object", + "additionalProperties": false + }, + "userInfo": { + "description": "UserInfo contains configuration for fetching user information from the upstream provider.\nWhen omitted and IdentityFromToken is also unset, the embedded auth server runs in\nsynthesis mode for this upstream: a non-PII subject derived from the access token, no\nName/Email. Use this shape for upstreams with no userinfo surface and no identity in\nthe token response (e.g., MCP authorization servers per the MCP spec). When\nIdentityFromToken is set instead, identity is resolved from the token response body\n(e.g., Snowflake's \"username\" field, Slack's \"authed_user.id\"); the userinfo HTTP call\nis skipped entirely.", + "properties": { + "additionalHeaders": { + "additionalProperties": { + "type": "string" + }, + "description": "AdditionalHeaders contains extra headers to include in the userinfo request.\nUseful for providers that require specific headers (e.g., GitHub's Accept header).", + "type": "object" + }, + "endpointUrl": { + "description": "EndpointURL is the URL of the userinfo endpoint.", + "pattern": "^https?://.*$", + "type": "string" + }, + "fieldMapping": { + "description": "FieldMapping contains custom field mapping configuration for non-standard providers.\nIf nil, standard OIDC field names are used (\"sub\", \"name\", \"email\").", + "properties": { + "emailFields": { + "description": "EmailFields is an ordered list of field names to try for the email address.\nThe first non-empty value found will be used.\nDefault: [\"email\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "nameFields": { + "description": "NameFields is an ordered list of field names to try for the display name.\nThe first non-empty value found will be used.\nDefault: [\"name\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "subjectFields": { + "description": "SubjectFields is an ordered list of field names to try for the user ID.\nThe first non-empty value found will be used.\nDefault: [\"sub\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "type": "object", + "additionalProperties": false + }, + "httpMethod": { + "description": "HTTPMethod is the HTTP method to use for the userinfo request.\nIf not specified, defaults to GET.", + "enum": [ + "GET", + "POST" + ], + "type": "string" + } + }, + "required": [ + "endpointUrl" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "authorizationEndpoint", + "tokenEndpoint" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "exactly one of clientId or dcrConfig must be set", + "rule": "(has(self.clientId) && size(self.clientId) > 0) ? !has(self.dcrConfig) : has(self.dcrConfig)" + }, + { + "message": "clientSecretRef must not be set when dcrConfig is set; the client_secret is obtained at runtime via Dynamic Client Registration", + "rule": "!(has(self.dcrConfig) && has(self.clientSecretRef))" + } + ], + "additionalProperties": false + }, + "oidcConfig": { + "description": "OIDCConfig contains OIDC-specific configuration.\nRequired when Type is \"oidc\", must be nil when Type is \"oauth2\".", + "properties": { + "additionalAuthorizationParams": { + "additionalProperties": { + "type": "string" + }, + "description": "AdditionalAuthorizationParams are extra query parameters to include in\nauthorization requests sent to the upstream provider.\nThis is useful for providers that require custom parameters, such as\nGoogle's access_type=offline for obtaining refresh tokens.\nNote: when using access_type=offline, also set explicit scopes to avoid\nthe default offline_access scope being sent alongside it.\nFramework-managed parameters (response_type, client_id, redirect_uri,\nscope, state, code_challenge, code_challenge_method, nonce) are not allowed.", + "maxProperties": 16, + "type": "object" + }, + "clientId": { + "description": "ClientID is the OAuth 2.0 client identifier registered with the upstream IDP.", + "type": "string" + }, + "clientSecretRef": { + "description": "ClientSecretRef references a Kubernetes Secret containing the OAuth 2.0 client secret.\nOptional for public clients using PKCE instead of client secret.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "issuerUrl": { + "description": "IssuerURL is the OIDC issuer URL for automatic endpoint discovery.\nMust be a valid HTTPS URL.", + "pattern": "^https://.*$", + "type": "string" + }, + "redirectUri": { + "description": "RedirectURI is the callback URL where the upstream IDP will redirect after authentication.\nWhen not specified, defaults to `{resourceUrl}/oauth/callback` where `resourceUrl` is the\nURL associated with the resource (e.g., MCPServer or vMCP) using this config.", + "type": "string" + }, + "scopes": { + "description": "Scopes are the OAuth scopes to request from the upstream IDP.\nIf not specified, defaults to [\"openid\", \"offline_access\"].\nWhen using additionalAuthorizationParams with provider-specific refresh token\nmechanisms (e.g., Google's access_type=offline), set explicit scopes to avoid\nsending both offline_access and the provider-specific parameter.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "userInfoOverride": { + "description": "UserInfoOverride allows customizing UserInfo fetching behavior for OIDC providers.\nBy default, the UserInfo endpoint is discovered automatically via OIDC discovery.\nUse this to override the endpoint URL, HTTP method, or field mappings for providers\nthat return non-standard claim names in their UserInfo response.", + "properties": { + "additionalHeaders": { + "additionalProperties": { + "type": "string" + }, + "description": "AdditionalHeaders contains extra headers to include in the userinfo request.\nUseful for providers that require specific headers (e.g., GitHub's Accept header).", + "type": "object" + }, + "endpointUrl": { + "description": "EndpointURL is the URL of the userinfo endpoint.", + "pattern": "^https?://.*$", + "type": "string" + }, + "fieldMapping": { + "description": "FieldMapping contains custom field mapping configuration for non-standard providers.\nIf nil, standard OIDC field names are used (\"sub\", \"name\", \"email\").", + "properties": { + "emailFields": { + "description": "EmailFields is an ordered list of field names to try for the email address.\nThe first non-empty value found will be used.\nDefault: [\"email\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "nameFields": { + "description": "NameFields is an ordered list of field names to try for the display name.\nThe first non-empty value found will be used.\nDefault: [\"name\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "subjectFields": { + "description": "SubjectFields is an ordered list of field names to try for the user ID.\nThe first non-empty value found will be used.\nDefault: [\"sub\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "type": "object", + "additionalProperties": false + }, + "httpMethod": { + "description": "HTTPMethod is the HTTP method to use for the userinfo request.\nIf not specified, defaults to GET.", + "enum": [ + "GET", + "POST" + ], + "type": "string" + } + }, + "required": [ + "endpointUrl" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "clientId", + "issuerUrl" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type specifies the provider type: \"oidc\" or \"oauth2\"", + "enum": [ + "oidc", + "oauth2" + ], + "type": "string" + } + }, + "required": [ + "name", + "type" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "type must be 'oidc' or 'oauth2'; oidcConfig must be set when type is 'oidc' and oauth2Config must be set when type is 'oauth2' (and the other must not be set)", + "rule": "self.type == 'oidc' ? (has(self.oidcConfig) && !has(self.oauth2Config)) : self.type == 'oauth2' ? (has(self.oauth2Config) && !has(self.oidcConfig)) : false" + } + ], + "additionalProperties": false + }, + "minItems": 1, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "required": [ + "issuer", + "upstreamProviders" + ], + "type": "object", + "additionalProperties": false + }, + "config": { + "description": "Config is the Virtual MCP server configuration.\nThe audit config from here is also supported, but not required.", + "properties": { + "aggregation": { + "description": "Aggregation defines tool aggregation and conflict resolution strategies.\nSupports ToolConfigRef for Kubernetes-native MCPToolConfig resource references.", + "properties": { + "conflictResolution": { + "default": "prefix", + "description": "ConflictResolution defines the strategy for resolving tool name conflicts.\n- prefix: Automatically prefix tool names with workload identifier\n- priority: First workload in priority order wins\n- manual: Explicitly define overrides for all conflicts", + "enum": [ + "prefix", + "priority", + "manual" + ], + "type": "string" + }, + "conflictResolutionConfig": { + "description": "ConflictResolutionConfig provides configuration for the chosen strategy.", + "properties": { + "prefixFormat": { + "default": "{workload}_", + "description": "PrefixFormat defines the prefix format for the \"prefix\" strategy.\nSupports placeholders: {workload}, {workload}_, {workload}.", + "type": "string" + }, + "priorityOrder": { + "description": "PriorityOrder defines the workload priority order for the \"priority\" strategy.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "additionalProperties": false + }, + "excludeAllTools": { + "description": "ExcludeAllTools hides all backend tools from MCP clients when true.\nHidden tools are NOT advertised in tools/list responses, but they ARE\navailable in the routing table for composite tools to use.\nThis enables the use case where you want to hide raw backend tools from\ndirect client access while exposing curated composite tool workflows.", + "type": "boolean" + }, + "tools": { + "description": "Tools defines per-workload tool filtering and overrides.", + "items": { + "description": "WorkloadToolConfig defines tool filtering and overrides for a specific workload.", + "properties": { + "excludeAll": { + "description": "ExcludeAll hides all tools from this workload from MCP clients when true.\nHidden tools are NOT advertised in tools/list responses, but they ARE\navailable in the routing table for composite tools to use.\nThis enables the use case where you want to hide raw backend tools from\ndirect client access while exposing curated composite tool workflows.", + "type": "boolean" + }, + "filter": { + "description": "Filter is an allow-list of tool names to advertise to MCP clients.\nTools NOT in this list are hidden from clients (not in tools/list response)\nbut remain available in the routing table for composite tools to use.\nThis enables selective exposure of backend tools while allowing composite\nworkflows to orchestrate all backend capabilities.\nOnly used if ToolConfigRef is not specified.", + "items": { + "type": "string" + }, + "type": "array" + }, + "overrides": { + "additionalProperties": { + "description": "ToolOverride defines tool name, description, and annotation overrides.", + "properties": { + "annotations": { + "description": "Annotations overrides specific tool annotation fields.\nOnly specified fields are overridden; others pass through from the backend.", + "properties": { + "destructiveHint": { + "description": "DestructiveHint overrides the destructive hint annotation.", + "type": "boolean" + }, + "idempotentHint": { + "description": "IdempotentHint overrides the idempotent hint annotation.", + "type": "boolean" + }, + "openWorldHint": { + "description": "OpenWorldHint overrides the open-world hint annotation.", + "type": "boolean" + }, + "readOnlyHint": { + "description": "ReadOnlyHint overrides the read-only hint annotation.", + "type": "boolean" + }, + "title": { + "description": "Title overrides the human-readable title annotation.", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "description": { + "description": "Description is the new tool description.", + "type": "string" + }, + "name": { + "description": "Name is the new tool name (for renaming).", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "description": "Overrides is an inline map of tool overrides for renaming and description changes.\nOverrides are applied to tools before conflict resolution and affect both\nadvertising and routing (the overridden name is used everywhere).\nOnly used if ToolConfigRef is not specified.", + "type": "object" + }, + "toolConfigRef": { + "description": "ToolConfigRef references an MCPToolConfig resource for tool filtering and renaming.\nIf specified, Filter and Overrides are ignored.\nOnly used when running in Kubernetes with the operator.", + "properties": { + "name": { + "description": "Name is the name of the MCPToolConfig resource in the same namespace.", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "workload": { + "description": "Workload is the name of the backend MCPServer workload.", + "type": "string" + } + }, + "required": [ + "workload" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + } + }, + "type": "object", + "additionalProperties": false + }, + "audit": { + "description": "Audit configures audit logging for the Virtual MCP server.\nWhen present, audit logs include MCP protocol operations.\nSee audit.Config for available configuration options.", + "properties": { + "component": { + "description": "Component is the component name to use in audit events.", + "type": "string" + }, + "detectApplicationErrors": { + "default": true, + "description": "DetectApplicationErrors controls whether the audit middleware inspects\nJSON-RPC response bodies for application-level errors when the HTTP\nstatus code indicates success (2xx). When enabled, a small prefix of\nthe response body is buffered to detect JSON-RPC error fields,\nindependent of the IncludeResponseData setting.", + "type": "boolean" + }, + "enabled": { + "default": false, + "description": "Enabled controls whether audit logging is enabled.\nWhen true, enables audit logging with the configured options.", + "type": "boolean" + }, + "eventTypes": { + "description": "EventTypes specifies which event types to audit. If empty, all events are audited.", + "items": { + "type": "string" + }, + "type": "array" + }, + "excludeEventTypes": { + "description": "ExcludeEventTypes specifies which event types to exclude from auditing.\nThis takes precedence over EventTypes.", + "items": { + "type": "string" + }, + "type": "array" + }, + "includeRequestData": { + "default": false, + "description": "IncludeRequestData determines whether to include request data in audit logs.", + "type": "boolean" + }, + "includeResponseData": { + "default": false, + "description": "IncludeResponseData determines whether to include response data in audit logs.", + "type": "boolean" + }, + "logFile": { + "description": "LogFile specifies the file path for audit logs. If empty, logs to stdout.", + "type": "string" + }, + "maxDataSize": { + "default": 1024, + "description": "MaxDataSize limits the size of request/response data included in audit logs (in bytes).", + "type": "integer" + } + }, + "type": "object", + "additionalProperties": false + }, + "backends": { + "description": "Backends defines pre-configured backend servers for static mode.\nWhen OutgoingAuth.Source is \"inline\", this field contains the full list of backend\nservers with their URLs and transport types, eliminating the need for K8s API access.\nWhen OutgoingAuth.Source is \"discovered\", this field is empty and backends are\ndiscovered at runtime via Kubernetes API.", + "items": { + "description": "StaticBackendConfig defines a pre-configured backend server for static mode.\nThis allows vMCP to operate without Kubernetes API access by embedding all backend\ninformation directly in the configuration.", + "properties": { + "caBundlePath": { + "description": "CABundlePath is the file path to a custom CA certificate bundle for TLS verification.\nOnly valid when Type is \"entry\". The operator mounts CA bundles at\n/etc/toolhive/ca-bundles//ca.crt.", + "type": "string" + }, + "metadata": { + "additionalProperties": { + "type": "string" + }, + "description": "Metadata is a custom key-value map for storing additional backend information\nsuch as labels, tags, or other arbitrary data (e.g., \"env\": \"prod\", \"region\": \"us-east-1\").\nThis is NOT Kubernetes ObjectMeta - it's a simple string map for user-defined metadata.\nReserved keys: \"group\" is automatically set by vMCP and any user-provided value will be overridden.", + "type": "object" + }, + "name": { + "description": "Name is the backend identifier.\nMust match the backend name from the MCPGroup for auth config resolution.", + "type": "string" + }, + "transport": { + "description": "Transport is the MCP transport protocol: \"sse\" or \"streamable-http\"\nOnly network transports supported by vMCP client are allowed.", + "enum": [ + "sse", + "streamable-http" + ], + "type": "string" + }, + "type": { + "description": "Type is the backend workload type: \"entry\" for MCPServerEntry backends, or empty\nfor container/proxy backends. Entry backends connect directly to remote MCP servers.", + "enum": [ + "entry", + "" + ], + "type": "string" + }, + "url": { + "description": "URL is the backend's MCP server base URL.", + "pattern": "^https?://", + "type": "string" + } + }, + "required": [ + "name", + "transport", + "url" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + }, + "compositeToolRefs": { + "description": "CompositeToolRefs references VirtualMCPCompositeToolDefinition resources\nfor complex, reusable workflows. Only applicable when running in Kubernetes.\nReferenced resources must be in the same namespace as the VirtualMCPServer.", + "items": { + "description": "CompositeToolRef defines a reference to a VirtualMCPCompositeToolDefinition resource.\nThe referenced resource must be in the same namespace as the VirtualMCPServer.", + "properties": { + "name": { + "description": "Name is the name of the VirtualMCPCompositeToolDefinition resource in the same namespace.", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + }, + "compositeTools": { + "description": "CompositeTools defines inline composite tool workflows.\nFull workflow definitions are embedded in the configuration.\nFor Kubernetes, complex workflows can also reference VirtualMCPCompositeToolDefinition CRDs.", + "items": { + "description": "CompositeToolConfig defines a composite tool workflow.\nThis matches the YAML structure from the proposal (lines 173-255).", + "properties": { + "description": { + "description": "Description describes what the workflow does.", + "type": "string" + }, + "name": { + "description": "Name is the workflow name (unique identifier).", + "type": "string" + }, + "output": { + "description": "Output defines the structured output schema for this workflow.\nIf not specified, the workflow returns the last step's output (backward compatible).", + "properties": { + "properties": { + "additionalProperties": { + "description": "OutputProperty defines a single output property.\nFor non-object types, Value is required.\nFor object types, either Value or Properties must be specified (but not both).", + "properties": { + "default": { + "description": "Default is the fallback value if template expansion fails.\nType coercion is applied to match the declared Type.", + "x-kubernetes-preserve-unknown-fields": true + }, + "description": { + "description": "Description is a human-readable description exposed to clients and models", + "type": "string" + }, + "properties": { + "description": "Properties defines nested properties for object types.\nEach nested property has full metadata (type, description, value/properties).", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "type": { + "description": "Type is the JSON Schema type: \"string\", \"integer\", \"number\", \"boolean\", \"object\", \"array\"", + "enum": [ + "string", + "integer", + "number", + "boolean", + "object", + "array" + ], + "type": "string" + }, + "value": { + "description": "Value is a template string for constructing the runtime value.\nFor object types, this can be a JSON string that will be deserialized.\nSupports template syntax: {{.steps.step_id.output.field}}, {{.params.param_name}}", + "type": "string" + }, + "additionalProperties": false + }, + "required": [ + "type" + ], + "type": "object", + "additionalProperties": false + }, + "description": "Properties defines the output properties.\nMap key is the property name, value is the property definition.", + "type": "object" + }, + "required": { + "description": "Required lists property names that must be present in the output.", + "items": { + "type": "string" + }, + "type": "array" + }, + "additionalProperties": false + }, + "required": [ + "properties" + ], + "type": "object", + "additionalProperties": false + }, + "parameters": { + "description": "Parameters defines input parameter schema in JSON Schema format.\nShould be a JSON Schema object with \"type\": \"object\" and \"properties\".\nExample:\n {\n \"type\": \"object\",\n \"properties\": {\n \"param1\": {\"type\": \"string\", \"default\": \"value\"},\n \"param2\": {\"type\": \"integer\"}\n },\n \"required\": [\"param2\"]\n }\n\nWe use json.Map rather than a typed struct because JSON Schema is highly\nflexible with many optional fields (default, enum, minimum, maximum, pattern,\nitems, additionalProperties, oneOf, anyOf, allOf, etc.). Using json.Map\nallows full JSON Schema compatibility without needing to define every possible\nfield, and matches how the MCP SDK handles inputSchema.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "steps": { + "description": "Steps are the workflow steps to execute.", + "items": { + "description": "WorkflowStepConfig defines a single workflow step.\nThis matches the proposal's step configuration (lines 180-255).", + "properties": { + "arguments": { + "description": "Arguments is a map of argument values with template expansion support.\nSupports Go template syntax with .params and .steps for string values.\nNon-string values (integers, booleans, arrays, objects) are passed as-is.\nNote: the templating is only supported on the first level of the key-value pairs.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "collection": { + "description": "Collection is a Go template expression that resolves to a JSON array or a slice.\nOnly used when Type is \"forEach\".", + "type": "string" + }, + "condition": { + "description": "Condition is a template expression that determines if the step should execute", + "type": "string" + }, + "defaultResults": { + "description": "DefaultResults provides fallback output values when this step is skipped\n(due to condition evaluating to false) or fails (when onError.action is \"continue\").\nEach key corresponds to an output field name referenced by downstream steps.\nRequired if the step may be skipped AND downstream steps reference this step's output.", + "x-kubernetes-preserve-unknown-fields": true + }, + "dependsOn": { + "description": "DependsOn lists step IDs that must complete before this step", + "items": { + "type": "string" + }, + "type": "array" + }, + "id": { + "description": "ID is the unique identifier for this step.", + "type": "string" + }, + "itemVar": { + "description": "ItemVar is the variable name used to reference the current item in forEach templates.\nDefaults to \"item\" if not specified.\nOnly used when Type is \"forEach\".", + "type": "string" + }, + "maxIterations": { + "description": "MaxIterations limits the number of items that can be iterated over.\nDefaults to 100, hard cap at 1000.\nOnly used when Type is \"forEach\".", + "type": "integer" + }, + "maxParallel": { + "description": "MaxParallel limits the number of concurrent iterations in a forEach step.\nDefaults to the DAG executor's maxParallel (10).\nOnly used when Type is \"forEach\".", + "type": "integer" + }, + "message": { + "description": "Message is the elicitation message\nOnly used when Type is \"elicitation\"", + "type": "string" + }, + "onCancel": { + "description": "OnCancel defines the action to take when the user cancels/dismisses the elicitation\nOnly used when Type is \"elicitation\"", + "properties": { + "action": { + "default": "abort", + "description": "Action defines the action to take when the user declines or cancels\n- skip_remaining: Skip remaining steps in the workflow\n- abort: Abort the entire workflow execution\n- continue: Continue to the next step", + "enum": [ + "skip_remaining", + "abort", + "continue" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "onDecline": { + "description": "OnDecline defines the action to take when the user explicitly declines the elicitation\nOnly used when Type is \"elicitation\"", + "properties": { + "action": { + "default": "abort", + "description": "Action defines the action to take when the user declines or cancels\n- skip_remaining: Skip remaining steps in the workflow\n- abort: Abort the entire workflow execution\n- continue: Continue to the next step", + "enum": [ + "skip_remaining", + "abort", + "continue" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "onError": { + "description": "OnError defines error handling behavior", + "properties": { + "action": { + "default": "abort", + "description": "Action defines the action to take on error", + "enum": [ + "abort", + "continue", + "retry" + ], + "type": "string" + }, + "retryCount": { + "description": "RetryCount is the maximum number of retries\nOnly used when Action is \"retry\"", + "type": "integer" + }, + "retryDelay": { + "description": "RetryDelay is the delay between retry attempts\nOnly used when Action is \"retry\"", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "schema": { + "description": "Schema defines the expected response schema for elicitation", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "step": { + "description": "InnerStep defines the step to execute for each item in the collection.\nOnly used when Type is \"forEach\". Only tool-type inner steps are supported.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "timeout": { + "description": "Timeout is the maximum execution time for this step", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "tool": { + "description": "Tool is the tool to call (format: \"workload.tool_name\")\nOnly used when Type is \"tool\"", + "type": "string" + }, + "type": { + "default": "tool", + "description": "Type is the step type (tool, elicitation, etc.)", + "enum": [ + "tool", + "elicitation", + "forEach" + ], + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + }, + "timeout": { + "description": "Timeout is the maximum workflow execution time.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + } + }, + "required": [ + "name", + "steps" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + }, + "groupRef": { + "description": "Group references an existing MCPGroup that defines backend workloads.\nIn standalone CLI mode, this is set from the YAML config file.\nIn Kubernetes, the operator populates this from spec.groupRef during conversion.", + "type": "string" + }, + "incomingAuth": { + "description": "IncomingAuth configures how clients authenticate to the virtual MCP server.\nWhen using the Kubernetes operator, this is populated by the converter from\nVirtualMCPServerSpec.IncomingAuth and any values set here will be superseded.", + "properties": { + "authz": { + "description": "Authz contains authorization configuration (optional).", + "properties": { + "entitiesJson": { + "description": "EntitiesJSON is a JSON string representing Cedar entities. Required for\nenterprise policies that rely on transitive relationships (e.g.\n`ClaimGroup \u2192 PlatformRole`) \u2014 without it the Cedar authorizer is\nconstructed with an empty entity store and `in` checks against absent\nentities silently evaluate to false. Defaults to \"[]\" when empty.", + "type": "string" + }, + "groupClaimName": { + "description": "GroupClaimName is the JWT claim key that contains group membership for\nthe principal. When set, takes priority over the well-known defaults\n(\"groups\", \"roles\", \"cognito:groups\"). Use this for IDPs that place\ngroups under a URI-style claim (e.g. \"https://example.com/groups\").\nWhen empty, only the well-known claim names are checked.", + "type": "string" + }, + "groupEntityType": { + "description": "GroupEntityType is the Cedar entity type name used for principal parent\nUIDs synthesised from JWT group/role claims. Defaults to \"THVGroup\" when\nempty. Must match the entity type used in EntitiesJSON for transitive\n`in` checks to resolve. Namespaced names (`Foo::Bar`) are not yet supported.", + "type": "string" + }, + "policies": { + "description": "Policies contains Cedar policy definitions (when Type = \"cedar\").", + "items": { + "type": "string" + }, + "type": "array" + }, + "primaryUpstreamProvider": { + "description": "PrimaryUpstreamProvider names the upstream IDP provider whose access\ntoken should be used as the source of JWT claims for Cedar evaluation.\nWhen empty, claims from the ToolHive-issued token are used.\nMust match an upstream provider name configured in the embedded auth server\n(e.g. \"default\", \"github\"). Only relevant when the embedded auth server is active.", + "type": "string" + }, + "roleClaimName": { + "description": "RoleClaimName is the JWT claim key that contains role membership for the\nprincipal. When set, the claim is extracted separately from GroupClaimName\nand both are mapped to the configured group entity type. When empty, no\nrole extraction is performed.", + "type": "string" + }, + "type": { + "description": "Type is the authz type: \"cedar\", \"none\"", + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "additionalProperties": false + }, + "oidc": { + "description": "OIDC contains OIDC configuration (when Type = \"oidc\").", + "properties": { + "audience": { + "description": "Audience is the required token audience.", + "type": "string" + }, + "clientId": { + "description": "ClientID is the OAuth client ID.", + "type": "string" + }, + "clientSecretEnv": { + "description": "ClientSecretEnv is the name of the environment variable containing the client secret.\nThis is the secure way to reference secrets - the actual secret value is never stored\nin configuration files, only the environment variable name.\nThe secret value will be resolved from this environment variable at runtime.", + "type": "string" + }, + "insecureAllowHttp": { + "description": "InsecureAllowHTTP allows HTTP (non-HTTPS) OIDC issuers for development/testing\nWARNING: This is insecure and should NEVER be used in production", + "type": "boolean" + }, + "introspectionUrl": { + "description": "IntrospectionURL is the token introspection endpoint URL (RFC 7662).\nWhen set, enables token introspection for opaque (non-JWT) tokens.", + "type": "string" + }, + "issuer": { + "description": "Issuer is the OIDC issuer URL.", + "pattern": "^https?://", + "type": "string" + }, + "jwksAllowPrivateIp": { + "description": "JwksAllowPrivateIP allows OIDC discovery and JWKS fetches to private IP addresses.\nEnable when the embedded auth server runs on a loopback address and\nthe OIDC middleware needs to fetch its JWKS from that address.\nUse with caution - only enable for trusted internal IDPs or testing.", + "type": "boolean" + }, + "jwksUrl": { + "description": "JWKSURL is the explicit JWKS endpoint URL.\nWhen set, skips OIDC discovery and fetches the JWKS directly from this URL.\nThis is useful when the OIDC issuer does not serve a /.well-known/openid-configuration.", + "type": "string" + }, + "protectedResourceAllowPrivateIp": { + "description": "ProtectedResourceAllowPrivateIP allows protected resource endpoint on private IP addresses\nUse with caution - only enable for trusted internal IDPs or testing", + "type": "boolean" + }, + "resource": { + "description": "Resource is the OAuth 2.0 resource indicator (RFC 8707).\nUsed in WWW-Authenticate header and OAuth discovery metadata (RFC 9728).\nIf not specified, defaults to Audience.", + "type": "string" + }, + "scopes": { + "description": "Scopes are the required OAuth scopes.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "audience", + "clientId", + "issuer" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type is the auth type: \"oidc\", \"local\", \"anonymous\"", + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "additionalProperties": false + }, + "metadata": { + "additionalProperties": { + "type": "string" + }, + "description": "Metadata stores additional configuration metadata.", + "type": "object" + }, + "name": { + "description": "Name is the virtual MCP server name.", + "type": "string" + }, + "operational": { + "description": "Operational configures operational settings.", + "properties": { + "failureHandling": { + "description": "FailureHandling configures failure handling behavior.", + "properties": { + "circuitBreaker": { + "description": "CircuitBreaker configures circuit breaker behavior.", + "properties": { + "enabled": { + "default": false, + "description": "Enabled controls whether circuit breaker is enabled.", + "type": "boolean" + }, + "failureThreshold": { + "default": 5, + "description": "FailureThreshold is the number of failures before opening the circuit.\nMust be >= 1.", + "minimum": 1, + "type": "integer" + }, + "timeout": { + "default": "60s", + "description": "Timeout is the duration to wait before attempting to close the circuit.\nMust be >= 1s to prevent thrashing.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string", + "x-kubernetes-validations": [ + { + "message": "timeout must be >= 1s", + "rule": "self == '' || duration(self) >= duration('1s')" + } + ] + } + }, + "type": "object", + "additionalProperties": false + }, + "healthCheckInterval": { + "default": "30s", + "description": "HealthCheckInterval is the interval between health checks.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "healthCheckTimeout": { + "default": "10s", + "description": "HealthCheckTimeout is the maximum duration for a single health check operation.\nShould be less than HealthCheckInterval to prevent checks from queuing up.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "partialFailureMode": { + "default": "fail", + "description": "PartialFailureMode defines behavior when some backends are unavailable.\n- fail: Fail entire request if any backend is unavailable\n- best_effort: Continue with available backends", + "enum": [ + "fail", + "best_effort" + ], + "type": "string" + }, + "statusReportingInterval": { + "default": "30s", + "description": "StatusReportingInterval is the interval for reporting status updates to Kubernetes.\nThis controls how often the vMCP runtime reports backend health and phase changes.\nLower values provide faster status updates but increase API server load.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "unhealthyThreshold": { + "default": 3, + "description": "UnhealthyThreshold is the number of consecutive failures before marking unhealthy.", + "type": "integer" + } + }, + "type": "object", + "additionalProperties": false + }, + "logLevel": { + "description": "LogLevel sets the logging level for the Virtual MCP server.\nThe only valid value is \"debug\" to enable debug logging.\nWhen omitted or empty, the server uses info level logging.", + "enum": [ + "debug" + ], + "type": "string" + }, + "timeouts": { + "description": "Timeouts configures timeout settings.", + "properties": { + "default": { + "default": "30s", + "description": "Default is the default timeout for backend requests.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "perWorkload": { + "additionalProperties": { + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "description": "PerWorkload defines per-workload timeout overrides.", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "optimizer": { + "description": "Optimizer configures the MCP optimizer for context optimization on large toolsets.\nWhen enabled, vMCP exposes only find_tool and call_tool operations to clients\ninstead of all backend tools directly. This reduces token usage by allowing\nLLMs to discover relevant tools on demand rather than receiving all tool definitions.", + "properties": { + "embeddingService": { + "description": "EmbeddingService is the full base URL of the embedding service endpoint\n(e.g., http://my-embedding.default.svc.cluster.local:8080) for semantic\ntool discovery.\n\nIn a Kubernetes environment, it is more convenient to use the\nVirtualMCPServerSpec.EmbeddingServerRef field instead of setting this\ndirectly. EmbeddingServerRef references an EmbeddingServer CRD by name,\nand the operator automatically resolves the referenced resource's\nStatus.URL to populate this field. This provides managed lifecycle\n(the operator watches the EmbeddingServer for readiness and URL changes)\nand avoids hardcoding service URLs in the config. If both\nEmbeddingServerRef and this field are set, EmbeddingServerRef takes\nprecedence and this value is overridden with a warning.", + "type": "string" + }, + "embeddingServiceTimeout": { + "default": "30s", + "description": "EmbeddingServiceTimeout is the HTTP request timeout for calls to the embedding service.\nDefaults to 30s if not specified.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "hybridSearchSemanticRatio": { + "description": "HybridSearchSemanticRatio controls the balance between semantic (meaning-based)\nand keyword search results. 0.0 = all keyword, 1.0 = all semantic.\nDefaults to \"0.5\" if not specified or empty.\nSerialized as a string because CRDs do not support float types portably.", + "pattern": "^([0-9]*[.])?[0-9]+$", + "type": "string" + }, + "maxToolsToReturn": { + "description": "MaxToolsToReturn is the maximum number of tool results returned by a search query.\nDefaults to 8 if not specified or zero.", + "maximum": 50, + "minimum": 1, + "type": "integer" + }, + "semanticDistanceThreshold": { + "description": "SemanticDistanceThreshold is the maximum distance for semantic search results.\nResults exceeding this threshold are filtered out from semantic search.\nThis threshold does not apply to keyword search.\nRange: 0 = identical, 2 = completely unrelated.\nDefaults to \"1.0\" if not specified or empty.\nSerialized as a string because CRDs do not support float types portably.", + "pattern": "^([0-9]*[.])?[0-9]+$", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "outgoingAuth": { + "description": "OutgoingAuth configures how the virtual MCP server authenticates to backends.\nWhen using the Kubernetes operator, this is populated by the converter from\nVirtualMCPServerSpec.OutgoingAuth and any values set here will be superseded.", + "properties": { + "backends": { + "additionalProperties": { + "description": "BackendAuthStrategy defines how to authenticate to a specific backend.\n\nThis struct provides type-safe configuration for different authentication strategies\nusing HeaderInjection or TokenExchange fields based on the Type field.", + "properties": { + "awsSts": { + "description": "AwsSts contains configuration for AWS STS auth strategy.\nUsed when Type = \"aws_sts\".", + "properties": { + "fallbackRoleArn": { + "description": "FallbackRoleArn is the IAM role ARN to assume when no role mappings match.", + "type": "string" + }, + "region": { + "description": "Region is the AWS region for the STS endpoint and service.", + "type": "string" + }, + "roleClaim": { + "description": "RoleClaim is the JWT claim to use for role mapping evaluation.", + "type": "string" + }, + "roleMappings": { + "description": "RoleMappings defines claim-based role selection rules.", + "items": { + "description": "RoleMapping defines a rule for mapping JWT claims to IAM roles.\nMappings are evaluated in priority order (lower number = higher priority).", + "properties": { + "claim": { + "description": "Claim is a simple claim value to match against the RoleClaim field.", + "type": "string" + }, + "matcher": { + "description": "Matcher is a CEL expression for complex matching against JWT claims.", + "type": "string" + }, + "priority": { + "description": "Priority determines evaluation order (lower values = higher priority).\nMirrors awssts.RoleMapping.Priority, which is *int because the role mapper\nuses math.MaxInt for nil-priority semantics in effectivePriority.", + "type": "integer" + }, + "roleArn": { + "description": "RoleArn is the IAM role ARN to assume when this mapping matches.", + "type": "string" + } + }, + "required": [ + "roleArn" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "service": { + "description": "Service is the AWS service name for SigV4 signing.", + "type": "string" + }, + "sessionDuration": { + "description": "SessionDuration is the duration in seconds for the STS session.", + "format": "int32", + "type": "integer" + }, + "sessionNameClaim": { + "description": "SessionNameClaim is the JWT claim to use for the role session name.", + "type": "string" + }, + "subjectProviderName": { + "description": "SubjectProviderName selects which upstream provider's token to use as the\nweb identity token for AssumeRoleWithWebIdentity. When set, the token is\nlooked up from Identity.UpstreamTokens instead of the request's\nAuthorization header.", + "type": "string" + } + }, + "required": [ + "region" + ], + "type": "object", + "additionalProperties": false + }, + "headerInjection": { + "description": "HeaderInjection contains configuration for header injection auth strategy.\nUsed when Type = \"header_injection\".", + "properties": { + "headerName": { + "description": "HeaderName is the name of the header to inject (e.g., \"Authorization\").", + "type": "string" + }, + "headerValue": { + "description": "HeaderValue is the static header value to inject.\nEither HeaderValue or HeaderValueEnv should be set, not both.", + "type": "string" + }, + "headerValueEnv": { + "description": "HeaderValueEnv is the environment variable name containing the header value.\nThe value will be resolved at runtime from this environment variable.\nEither HeaderValue or HeaderValueEnv should be set, not both.", + "type": "string" + } + }, + "required": [ + "headerName" + ], + "type": "object", + "additionalProperties": false + }, + "tokenExchange": { + "description": "TokenExchange contains configuration for token exchange auth strategy.\nUsed when Type = \"token_exchange\".", + "properties": { + "audience": { + "description": "Audience is the target audience for the exchanged token.", + "type": "string" + }, + "clientId": { + "description": "ClientID is the OAuth client ID for the token exchange request.", + "type": "string" + }, + "clientSecret": { + "description": "ClientSecret is the OAuth client secret (use ClientSecretEnv for security).", + "type": "string" + }, + "clientSecretEnv": { + "description": "ClientSecretEnv is the environment variable name containing the client secret.\nThe value will be resolved at runtime from this environment variable.", + "type": "string" + }, + "scopes": { + "description": "Scopes are the requested scopes for the exchanged token.", + "items": { + "type": "string" + }, + "type": "array" + }, + "subjectProviderName": { + "description": "SubjectProviderName selects which upstream provider's token to use as the\nsubject token. When set, the token is looked up from Identity.UpstreamTokens\ninstead of using Identity.Token.\nWhen left empty and an embedded authorization server is configured, the system\nautomatically populates this field with the first configured upstream provider name.\nSet it explicitly to override that default or to select a specific provider when\nmultiple upstreams are configured.", + "type": "string" + }, + "subjectTokenType": { + "description": "SubjectTokenType is the token type of the incoming subject token.\nDefaults to \"urn:ietf:params:oauth:token-type:access_token\" if not specified.", + "type": "string" + }, + "tokenUrl": { + "description": "TokenURL is the OAuth token endpoint URL for token exchange.", + "type": "string" + } + }, + "required": [ + "tokenUrl" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type is the auth strategy: \"unauthenticated\", \"header_injection\", \"token_exchange\", \"upstream_inject\", \"aws_sts\", \"obo\"", + "type": "string" + }, + "upstreamInject": { + "description": "UpstreamInject contains configuration for upstream inject auth strategy.\nUsed when Type = \"upstream_inject\".", + "properties": { + "providerName": { + "description": "ProviderName is the name of the upstream provider configured in the\nembedded authorization server. Must match an entry in AuthServer.Upstreams.", + "type": "string" + } + }, + "required": [ + "providerName" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "type" + ], + "type": "object", + "additionalProperties": false + }, + "description": "Backends contains per-backend auth configuration.", + "type": "object" + }, + "default": { + "description": "Default is the default auth strategy for backends without explicit config.", + "properties": { + "awsSts": { + "description": "AwsSts contains configuration for AWS STS auth strategy.\nUsed when Type = \"aws_sts\".", + "properties": { + "fallbackRoleArn": { + "description": "FallbackRoleArn is the IAM role ARN to assume when no role mappings match.", + "type": "string" + }, + "region": { + "description": "Region is the AWS region for the STS endpoint and service.", + "type": "string" + }, + "roleClaim": { + "description": "RoleClaim is the JWT claim to use for role mapping evaluation.", + "type": "string" + }, + "roleMappings": { + "description": "RoleMappings defines claim-based role selection rules.", + "items": { + "description": "RoleMapping defines a rule for mapping JWT claims to IAM roles.\nMappings are evaluated in priority order (lower number = higher priority).", + "properties": { + "claim": { + "description": "Claim is a simple claim value to match against the RoleClaim field.", + "type": "string" + }, + "matcher": { + "description": "Matcher is a CEL expression for complex matching against JWT claims.", + "type": "string" + }, + "priority": { + "description": "Priority determines evaluation order (lower values = higher priority).\nMirrors awssts.RoleMapping.Priority, which is *int because the role mapper\nuses math.MaxInt for nil-priority semantics in effectivePriority.", + "type": "integer" + }, + "roleArn": { + "description": "RoleArn is the IAM role ARN to assume when this mapping matches.", + "type": "string" + } + }, + "required": [ + "roleArn" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "service": { + "description": "Service is the AWS service name for SigV4 signing.", + "type": "string" + }, + "sessionDuration": { + "description": "SessionDuration is the duration in seconds for the STS session.", + "format": "int32", + "type": "integer" + }, + "sessionNameClaim": { + "description": "SessionNameClaim is the JWT claim to use for the role session name.", + "type": "string" + }, + "subjectProviderName": { + "description": "SubjectProviderName selects which upstream provider's token to use as the\nweb identity token for AssumeRoleWithWebIdentity. When set, the token is\nlooked up from Identity.UpstreamTokens instead of the request's\nAuthorization header.", + "type": "string" + } + }, + "required": [ + "region" + ], + "type": "object", + "additionalProperties": false + }, + "headerInjection": { + "description": "HeaderInjection contains configuration for header injection auth strategy.\nUsed when Type = \"header_injection\".", + "properties": { + "headerName": { + "description": "HeaderName is the name of the header to inject (e.g., \"Authorization\").", + "type": "string" + }, + "headerValue": { + "description": "HeaderValue is the static header value to inject.\nEither HeaderValue or HeaderValueEnv should be set, not both.", + "type": "string" + }, + "headerValueEnv": { + "description": "HeaderValueEnv is the environment variable name containing the header value.\nThe value will be resolved at runtime from this environment variable.\nEither HeaderValue or HeaderValueEnv should be set, not both.", + "type": "string" + } + }, + "required": [ + "headerName" + ], + "type": "object", + "additionalProperties": false + }, + "tokenExchange": { + "description": "TokenExchange contains configuration for token exchange auth strategy.\nUsed when Type = \"token_exchange\".", + "properties": { + "audience": { + "description": "Audience is the target audience for the exchanged token.", + "type": "string" + }, + "clientId": { + "description": "ClientID is the OAuth client ID for the token exchange request.", + "type": "string" + }, + "clientSecret": { + "description": "ClientSecret is the OAuth client secret (use ClientSecretEnv for security).", + "type": "string" + }, + "clientSecretEnv": { + "description": "ClientSecretEnv is the environment variable name containing the client secret.\nThe value will be resolved at runtime from this environment variable.", + "type": "string" + }, + "scopes": { + "description": "Scopes are the requested scopes for the exchanged token.", + "items": { + "type": "string" + }, + "type": "array" + }, + "subjectProviderName": { + "description": "SubjectProviderName selects which upstream provider's token to use as the\nsubject token. When set, the token is looked up from Identity.UpstreamTokens\ninstead of using Identity.Token.\nWhen left empty and an embedded authorization server is configured, the system\nautomatically populates this field with the first configured upstream provider name.\nSet it explicitly to override that default or to select a specific provider when\nmultiple upstreams are configured.", + "type": "string" + }, + "subjectTokenType": { + "description": "SubjectTokenType is the token type of the incoming subject token.\nDefaults to \"urn:ietf:params:oauth:token-type:access_token\" if not specified.", + "type": "string" + }, + "tokenUrl": { + "description": "TokenURL is the OAuth token endpoint URL for token exchange.", + "type": "string" + } + }, + "required": [ + "tokenUrl" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type is the auth strategy: \"unauthenticated\", \"header_injection\", \"token_exchange\", \"upstream_inject\", \"aws_sts\", \"obo\"", + "type": "string" + }, + "upstreamInject": { + "description": "UpstreamInject contains configuration for upstream inject auth strategy.\nUsed when Type = \"upstream_inject\".", + "properties": { + "providerName": { + "description": "ProviderName is the name of the upstream provider configured in the\nembedded authorization server. Must match an entry in AuthServer.Upstreams.", + "type": "string" + } + }, + "required": [ + "providerName" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "type" + ], + "type": "object", + "additionalProperties": false + }, + "source": { + "description": "Source defines how to discover backend auth: \"inline\", \"discovered\"\n- inline: Explicit configuration in OutgoingAuth\n- discovered: Auto-discover from backend MCPServer.externalAuthConfigRef (Kubernetes only)", + "type": "string" + } + }, + "required": [ + "source" + ], + "type": "object", + "additionalProperties": false + }, + "rateLimiting": { + "description": "RateLimiting defines rate limiting configuration for the Virtual MCP server.\nRequires Redis session storage to be configured for distributed rate limiting.", + "properties": { + "perUser": { + "description": "PerUser is a token bucket applied independently to each authenticated user\nat the server level. Requires authentication to be enabled.\nEach unique userID creates Redis keys that expire after 2x refillPeriod.\nMemory formula: unique_users_per_TTL_window * (1 + num_tools_with_per_user_limits) keys.", + "properties": { + "maxTokens": { + "description": "MaxTokens is the maximum number of tokens (bucket capacity).\nThis is also the burst size: the maximum number of requests that can be served\ninstantaneously before the bucket is depleted.", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "refillPeriod": { + "description": "RefillPeriod is the duration to fully refill the bucket from zero to maxTokens.\nThe effective refill rate is maxTokens / refillPeriod tokens per second.\nFormat: Go duration string (e.g., \"1m0s\", \"30s\", \"1h0m0s\").", + "type": "string" + } + }, + "required": [ + "maxTokens", + "refillPeriod" + ], + "type": "object", + "additionalProperties": false + }, + "shared": { + "description": "Shared is a token bucket shared across all users for the entire server.", + "properties": { + "maxTokens": { + "description": "MaxTokens is the maximum number of tokens (bucket capacity).\nThis is also the burst size: the maximum number of requests that can be served\ninstantaneously before the bucket is depleted.", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "refillPeriod": { + "description": "RefillPeriod is the duration to fully refill the bucket from zero to maxTokens.\nThe effective refill rate is maxTokens / refillPeriod tokens per second.\nFormat: Go duration string (e.g., \"1m0s\", \"30s\", \"1h0m0s\").", + "type": "string" + } + }, + "required": [ + "maxTokens", + "refillPeriod" + ], + "type": "object", + "additionalProperties": false + }, + "tools": { + "description": "Tools defines per-tool rate limit overrides.\nEach entry applies additional rate limits to calls targeting a specific tool name.\nA request must pass both the server-level limit and the per-tool limit.", + "items": { + "description": "ToolRateLimitConfig defines rate limits for a specific tool.\nAt least one of shared or perUser must be configured.", + "properties": { + "name": { + "description": "Name is the MCP tool name this limit applies to.", + "minLength": 1, + "type": "string" + }, + "perUser": { + "description": "PerUser token bucket configuration for this tool.", + "properties": { + "maxTokens": { + "description": "MaxTokens is the maximum number of tokens (bucket capacity).\nThis is also the burst size: the maximum number of requests that can be served\ninstantaneously before the bucket is depleted.", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "refillPeriod": { + "description": "RefillPeriod is the duration to fully refill the bucket from zero to maxTokens.\nThe effective refill rate is maxTokens / refillPeriod tokens per second.\nFormat: Go duration string (e.g., \"1m0s\", \"30s\", \"1h0m0s\").", + "type": "string" + } + }, + "required": [ + "maxTokens", + "refillPeriod" + ], + "type": "object", + "additionalProperties": false + }, + "shared": { + "description": "Shared token bucket for this specific tool.", + "properties": { + "maxTokens": { + "description": "MaxTokens is the maximum number of tokens (bucket capacity).\nThis is also the burst size: the maximum number of requests that can be served\ninstantaneously before the bucket is depleted.", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "refillPeriod": { + "description": "RefillPeriod is the duration to fully refill the bucket from zero to maxTokens.\nThe effective refill rate is maxTokens / refillPeriod tokens per second.\nFormat: Go duration string (e.g., \"1m0s\", \"30s\", \"1h0m0s\").", + "type": "string" + } + }, + "required": [ + "maxTokens", + "refillPeriod" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "name" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "at least one of shared or perUser must be configured", + "rule": "has(self.shared) || has(self.perUser)" + } + ], + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "type": "object", + "x-kubernetes-validations": [ + { + "message": "at least one of shared, perUser, or tools must be configured", + "rule": "has(self.shared) || has(self.perUser) || (has(self.tools) && size(self.tools) > 0)" + } + ], + "additionalProperties": false + }, + "sessionStorage": { + "description": "SessionStorage configures session storage for stateful horizontal scaling.\nWhen provider is \"redis\", the operator injects Redis connection parameters\n(address, db, keyPrefix) here. The Redis password is provided separately via\nthe THV_SESSION_REDIS_PASSWORD environment variable.", + "properties": { + "address": { + "description": "Address is the Redis server address (required when provider is redis).", + "type": "string" + }, + "db": { + "default": 0, + "description": "DB is the Redis database number.", + "format": "int32", + "minimum": 0, + "type": "integer" + }, + "keyPrefix": { + "description": "KeyPrefix is an optional prefix for all Redis keys used by ToolHive.", + "type": "string" + }, + "provider": { + "description": "Provider is the session storage backend type.", + "enum": [ + "memory", + "redis" + ], + "type": "string" + } + }, + "required": [ + "provider" + ], + "type": "object", + "additionalProperties": false + }, + "telemetry": { + "description": "Telemetry configures OpenTelemetry-based observability for the Virtual MCP server\nincluding distributed tracing, OTLP metrics export, and Prometheus metrics endpoint.\nDeprecated (Kubernetes operator only): When deploying via the operator, use\nVirtualMCPServer.spec.telemetryConfigRef to reference a shared MCPTelemetryConfig\nresource instead. This field remains valid for standalone (non-operator) deployments.", + "properties": { + "caCertPath": { + "description": "CACertPath is the file path to a CA certificate bundle for the OTLP endpoint.\nWhen set, the OTLP exporters use this CA to verify the collector's TLS certificate\ninstead of relying solely on the system CA pool.", + "type": "string" + }, + "customAttributes": { + "additionalProperties": { + "type": "string" + }, + "description": "CustomAttributes contains custom resource attributes to be added to all telemetry signals.\nThese are parsed from CLI flags (--otel-custom-attributes) or environment variables\n(OTEL_RESOURCE_ATTRIBUTES) as key=value pairs.", + "type": "object" + }, + "enablePrometheusMetricsPath": { + "default": false, + "description": "EnablePrometheusMetricsPath controls whether to expose Prometheus-style /metrics endpoint.\nThe metrics are served on the main transport port at /metrics.\nThis is separate from OTLP metrics which are sent to the Endpoint.", + "type": "boolean" + }, + "endpoint": { + "description": "Endpoint is the OTLP endpoint URL", + "type": "string" + }, + "environmentVariables": { + "description": "EnvironmentVariables is a list of environment variable names that should be\nincluded in telemetry spans as attributes. Only variables in this list will\nbe read from the host machine and included in spans for observability.\nExample: [\"NODE_ENV\", \"DEPLOYMENT_ENV\", \"SERVICE_VERSION\"]", + "items": { + "type": "string" + }, + "type": "array" + }, + "headers": { + "additionalProperties": { + "type": "string" + }, + "description": "Headers contains authentication headers for the OTLP endpoint.", + "type": "object" + }, + "insecure": { + "default": false, + "description": "Insecure indicates whether to use HTTP instead of HTTPS for the OTLP endpoint.", + "type": "boolean" + }, + "metricsEnabled": { + "default": false, + "description": "MetricsEnabled controls whether OTLP metrics are enabled.\nWhen false, OTLP metrics are not sent even if an endpoint is configured.\nThis is independent of EnablePrometheusMetricsPath.", + "type": "boolean" + }, + "samplingRate": { + "default": "0.05", + "description": "SamplingRate is the trace sampling rate (0.0-1.0) as a string.\nOnly used when TracingEnabled is true.\nExample: \"0.05\" for 5% sampling.", + "type": "string" + }, + "serviceName": { + "description": "ServiceName is the service name for telemetry.\nWhen omitted, defaults to the server name (e.g., VirtualMCPServer name).", + "type": "string" + }, + "serviceVersion": { + "description": "ServiceVersion is the service version for telemetry.\nWhen omitted, defaults to the ToolHive version.", + "type": "string" + }, + "tracingEnabled": { + "default": false, + "description": "TracingEnabled controls whether distributed tracing is enabled.\nWhen false, no tracer provider is created even if an endpoint is configured.", + "type": "boolean" + }, + "useLegacyAttributes": { + "default": true, + "description": "UseLegacyAttributes controls whether legacy (pre-MCP OTEL semconv) attribute names\nare emitted alongside the new standard attribute names. When true, spans include both\nold and new attribute names for backward compatibility with existing dashboards.\nCurrently defaults to true; this will change to false in a future release.", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "x-kubernetes-preserve-unknown-fields": true, + "additionalProperties": false + }, + "embeddingServerRef": { + "description": "EmbeddingServerRef references an existing EmbeddingServer resource by name.\nWhen the optimizer is enabled, this field is required to point to a ready EmbeddingServer\nthat provides embedding capabilities.\nThe referenced EmbeddingServer must exist in the same namespace and be ready.", + "properties": { + "name": { + "description": "Name is the name of the EmbeddingServer resource", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "groupRef": { + "description": "GroupRef references the MCPGroup that defines backend workloads.\nThe referenced MCPGroup must exist in the same namespace.", + "properties": { + "name": { + "description": "Name is the name of the MCPGroup resource in the same namespace", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "imagePullSecrets": { + "description": "ImagePullSecrets allows specifying image pull secrets for the vMCP workload.\nThese are applied to both the vMCP Deployment's PodSpec.ImagePullSecrets\nand to the operator-managed ServiceAccount the vMCP server runs as, so private\nimages are pullable through either path.\n\nMerge semantics with PodTemplateSpec:\nThe deployed PodSpec.ImagePullSecrets is the Kubernetes-native strategic-merge\nunion of this field and spec.podTemplateSpec.spec.imagePullSecrets, merged by\nthe patchStrategy:\"merge\" / patchMergeKey:\"name\" tags on corev1.PodSpec.\n - This field is rendered first as the controller-generated default.\n - spec.podTemplateSpec.spec.imagePullSecrets is then strategic-merge-patched\n on top, keyed by Name. Distinct names from the two sources are unioned in\n the resulting list; entries with the same Name are deduplicated and the\n PodTemplateSpec entry wins on overlap (user override).\n - Order in the resulting list is not guaranteed and should not be relied on:\n strategic merge by name is order-insensitive.\n - The operator-managed ServiceAccount's imagePullSecrets list is populated\n ONLY from this field. spec.podTemplateSpec.spec.imagePullSecrets does not\n reach the ServiceAccount because PodTemplateSpec has no notion of a\n ServiceAccount. To make a secret usable via the ServiceAccount path\n (e.g. for sidecars or init containers that pull images independently),\n list it here rather than under spec.podTemplateSpec.\n\nNote on cross-CRD consistency:\nMCPRegistry currently uses an atomic-replace strategy for its imagePullSecrets\n(the user-provided value replaces the controller-generated list rather than\nbeing merged on top). VirtualMCPServer follows the Kubernetes-native\nstrategic-merge-by-name behavior described above. Aligning the two is tracked\nas a separate follow-up; until then, manifests that set imagePullSecrets on\nboth CRDs will see different override behavior between them.", + "items": { + "description": "LocalObjectReference contains enough information to let you locate the\nreferenced object inside the same namespace.", + "properties": { + "name": { + "default": "", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + } + }, + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "incomingAuth": { + "description": "IncomingAuth configures authentication for clients connecting to the Virtual MCP server.\nMust be explicitly set - use \"anonymous\" type when no authentication is required.\nThis field takes precedence over config.IncomingAuth and should be preferred because it\nsupports Kubernetes-native secret references (SecretKeyRef, ConfigMapRef) for secure\ndynamic discovery of credentials, rather than requiring secrets to be embedded in config.", + "properties": { + "authzConfig": { + "description": "AuthzConfig defines authorization policy configuration\nReuses MCPServer authz patterns", + "properties": { + "configMap": { + "description": "ConfigMap references a ConfigMap containing authorization configuration\nOnly used when Type is \"configMap\"", + "properties": { + "key": { + "default": "authz.json", + "description": "Key is the key in the ConfigMap that contains the authorization configuration", + "type": "string" + }, + "name": { + "description": "Name is the name of the ConfigMap", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "groupClaimName": { + "description": "GroupClaimName is the JWT claim key that contains group membership for the\nprincipal. When set, takes priority over the well-known defaults\n(\"groups\", \"roles\", \"cognito:groups\"). Use this for IDPs that place\ngroups under a URI-style claim (e.g. \"https://example.com/groups\"). When\nType is \"configMap\", a group_claim_name entry in the referenced ConfigMap\nis overridden by this field if both are set.", + "maxLength": 253, + "type": "string" + }, + "groupEntityType": { + "description": "GroupEntityType is the Cedar entity type name used for principal parent\nUIDs synthesised from JWT group/role claims. Defaults to \"THVGroup\" when\nempty. Must match the entity type used in the static entity store for\ntransitive `in` checks (e.g. `ClaimGroup \u2192 PlatformRole`) to resolve.\nNamespaced names (`Foo::Bar`) are not yet supported. When Type is\n\"configMap\", a group_entity_type entry in the referenced ConfigMap is\noverridden by this field if both are set.", + "maxLength": 63, + "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", + "type": "string" + }, + "inline": { + "description": "Inline contains direct authorization configuration\nOnly used when Type is \"inline\"", + "properties": { + "entitiesJson": { + "default": "[]", + "description": "EntitiesJSON is a JSON string representing Cedar entities. Required when\ntransitive policies (e.g. `ClaimGroup \u2192 PlatformRole`) need a static\nentity store; defaults to \"[]\".", + "type": "string" + }, + "policies": { + "description": "Policies is a list of Cedar policy strings", + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "primaryUpstreamProvider": { + "description": "PrimaryUpstreamProvider names the upstream IDP whose access token's\nclaims Cedar should evaluate.\n\nDeprecated: on VirtualMCPServer this field has moved to\nspec.authServerConfig.primaryUpstreamProvider. The old location is\nstill read for one release for backward compatibility; the\nVirtualMCPServer controller emits an AuthzPrimaryUpstreamProviderDeprecated\nWarning event whenever it is consumed, and removal is planned for the\nrelease after the deprecation cycle.\n\nOn MCPServer and MCPRemoteProxy this field has always been a structural\nno-op (those CRDs do not run an embedded auth server). Setting it\ncontinues to surface the AuthzPrimaryUpstreamProviderIgnored advisory\ncondition; the deprecation does not change that behaviour.", + "maxLength": 63, + "minLength": 1, + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "type": "string" + } + }, + "required": [ + "policies" + ], + "type": "object", + "additionalProperties": false + }, + "roleClaimName": { + "description": "RoleClaimName is the JWT claim key that contains role membership for the\nprincipal. When set, the claim is extracted separately from GroupClaimName\nand both are mapped to the configured GroupEntityType. When Type is\n\"configMap\", a role_claim_name entry in the referenced ConfigMap is\noverridden by this field if both are set.", + "maxLength": 253, + "type": "string" + }, + "type": { + "default": "configMap", + "description": "Type is the type of authorization configuration", + "enum": [ + "configMap", + "inline" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "configMap must be set when type is 'configMap', and must not be set otherwise", + "rule": "self.type == 'configMap' ? has(self.configMap) : !has(self.configMap)" + }, + { + "message": "inline must be set when type is 'inline', and must not be set otherwise", + "rule": "self.type == 'inline' ? has(self.inline) : !has(self.inline)" + } + ], + "additionalProperties": false + }, + "oidcConfigRef": { + "description": "OIDCConfigRef references a shared MCPOIDCConfig resource for OIDC authentication.\nThe referenced MCPOIDCConfig must exist in the same namespace as this VirtualMCPServer.\nPer-server overrides (audience, scopes) are specified here; shared provider config\nlives in the MCPOIDCConfig resource.", + "properties": { + "audience": { + "description": "Audience is the expected audience for token validation.\nThis MUST be unique per server to prevent token replay attacks.", + "minLength": 1, + "type": "string" + }, + "name": { + "description": "Name is the name of the MCPOIDCConfig resource", + "minLength": 1, + "type": "string" + }, + "resourceUrl": { + "description": "ResourceURL is the public URL for OAuth protected resource metadata (RFC 9728).\nWhen the server is exposed via Ingress or gateway, set this to the external\nURL that MCP clients connect to. If not specified, defaults to the internal\nKubernetes service URL.", + "type": "string" + }, + "scopes": { + "description": "Scopes is the list of OAuth scopes to advertise in the well-known endpoint (RFC 9728).\nIf empty, defaults to [\"openid\"].", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "required": [ + "audience", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type defines the authentication type: anonymous or oidc\nWhen no authentication is required, explicitly set this to \"anonymous\"", + "enum": [ + "anonymous", + "oidc" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "spec.incomingAuth.oidcConfigRef is required when type is oidc", + "rule": "self.type == 'oidc' ? has(self.oidcConfigRef) : true" + } + ], + "additionalProperties": false + }, + "outgoingAuth": { + "description": "OutgoingAuth configures authentication from Virtual MCP to backend MCPServers.\nThis field takes precedence over config.OutgoingAuth and should be preferred because it\nsupports Kubernetes-native secret references (SecretKeyRef, ConfigMapRef) for secure\ndynamic discovery of credentials, rather than requiring secrets to be embedded in config.", + "properties": { + "backends": { + "additionalProperties": { + "description": "BackendAuthConfig defines authentication configuration for a backend MCPServer", + "properties": { + "externalAuthConfigRef": { + "description": "ExternalAuthConfigRef references an MCPExternalAuthConfig resource\nOnly used when Type is \"externalAuthConfigRef\"", + "properties": { + "name": { + "description": "Name is the name of the MCPExternalAuthConfig resource", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type defines the authentication type", + "enum": [ + "discovered", + "externalAuthConfigRef" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "additionalProperties": false + }, + "description": "Backends defines per-backend authentication overrides\nWorks in all modes (discovered, inline)", + "type": "object" + }, + "default": { + "description": "Default defines default behavior for backends without explicit auth config", + "properties": { + "externalAuthConfigRef": { + "description": "ExternalAuthConfigRef references an MCPExternalAuthConfig resource\nOnly used when Type is \"externalAuthConfigRef\"", + "properties": { + "name": { + "description": "Name is the name of the MCPExternalAuthConfig resource", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type defines the authentication type", + "enum": [ + "discovered", + "externalAuthConfigRef" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "additionalProperties": false + }, + "source": { + "default": "discovered", + "description": "Source defines how backend authentication configurations are determined\n- discovered: Automatically discover from backend's MCPServer.spec.externalAuthConfigRef\n- inline: Explicit per-backend configuration in VirtualMCPServer", + "enum": [ + "discovered", + "inline" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "podTemplateSpec": { + "description": "PodTemplateSpec defines the pod template to use for the Virtual MCP server\nThis allows for customizing the pod configuration beyond what is provided by the other fields.\nNote that to modify the specific container the Virtual MCP server runs in, you must specify\nthe 'vmcp' container name in the PodTemplateSpec.\nThis field accepts a PodTemplateSpec object as JSON/YAML.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "replicas": { + "description": "Replicas is the desired number of vMCP pod replicas.\nVirtualMCPServer creates a single Deployment for the vMCP aggregator process,\nso there is only one replicas field (unlike MCPServer which has separate\nReplicas and BackendReplicas for its two Deployments).\nWhen nil, the operator does not set Deployment.Spec.Replicas, leaving replica\nmanagement to an HPA or other external controller.", + "format": "int32", + "minimum": 0, + "type": "integer" + }, + "serviceAccount": { + "description": "ServiceAccount is the name of an already existing service account to use by the Virtual MCP server.\nIf not specified, a ServiceAccount will be created automatically and used by the Virtual MCP server.", + "type": "string" + }, + "serviceType": { + "default": "ClusterIP", + "description": "ServiceType specifies the Kubernetes service type for the Virtual MCP server", + "enum": [ + "ClusterIP", + "NodePort", + "LoadBalancer" + ], + "type": "string" + }, + "sessionAffinity": { + "default": "ClientIP", + "description": "SessionAffinity controls whether the Service routes repeated client connections to the same pod.\nMCP protocols (SSE, streamable-http) are stateful, so ClientIP is the default.\nSet to \"None\" for stateless servers or when using an external load balancer with its own affinity.", + "enum": [ + "ClientIP", + "None" + ], + "type": "string" + }, + "sessionStorage": { + "description": "SessionStorage configures session storage for stateful horizontal scaling.\nWhen nil, no session storage is configured.", + "properties": { + "address": { + "description": "Address is the Redis server address (required when provider is redis)", + "minLength": 1, + "type": "string" + }, + "db": { + "default": 0, + "description": "DB is the Redis database number", + "format": "int32", + "minimum": 0, + "type": "integer" + }, + "keyPrefix": { + "description": "KeyPrefix is an optional prefix for all Redis keys used by ToolHive", + "type": "string" + }, + "passwordRef": { + "description": "PasswordRef is a reference to a Secret key containing the Redis password", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "provider": { + "description": "Provider is the session storage backend type", + "enum": [ + "memory", + "redis" + ], + "type": "string" + } + }, + "required": [ + "provider" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "address is required", + "rule": "self.provider == 'redis' ? has(self.address) : true" + } + ], + "additionalProperties": false + }, + "telemetryConfigRef": { + "description": "TelemetryConfigRef references an MCPTelemetryConfig resource for shared telemetry configuration.\nThe referenced MCPTelemetryConfig must exist in the same namespace as this VirtualMCPServer.\nCross-namespace references are not supported for security and isolation reasons.", + "properties": { + "name": { + "description": "Name is the name of the MCPTelemetryConfig resource", + "minLength": 1, + "type": "string" + }, + "serviceName": { + "description": "ServiceName overrides the telemetry service name for this specific server.\nThis MUST be unique per server for proper observability (e.g., distinguishing\ntraces and metrics from different servers sharing the same collector).\nIf empty, defaults to the server name with \"thv-\" prefix at runtime.", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "groupRef", + "incomingAuth" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "config.rateLimiting requires sessionStorage with provider 'redis'", + "rule": "!has(self.config) || !has(self.config.rateLimiting) || (has(self.sessionStorage) && self.sessionStorage.provider == 'redis')" + }, + { + "message": "config.rateLimiting.perUser requires incomingAuth.type oidc", + "rule": "!(has(self.config) && has(self.config.rateLimiting) && has(self.config.rateLimiting.perUser)) || (has(self.incomingAuth) && self.incomingAuth.type == 'oidc')" + }, + { + "message": "per-tool perUser rate limiting requires incomingAuth.type oidc", + "rule": "!has(self.config) || !has(self.config.rateLimiting) || !has(self.config.rateLimiting.tools) || self.config.rateLimiting.tools.all(t, !has(t.perUser)) || (has(self.incomingAuth) && self.incomingAuth.type == 'oidc')" + } + ], + "additionalProperties": false + }, + "status": { + "description": "VirtualMCPServerStatus defines the observed state of VirtualMCPServer", + "properties": { + "backendCount": { + "description": "BackendCount is the number of routable backends (ready + unauthenticated).\nExcludes unavailable, degraded, and unknown backends.", + "format": "int32", + "type": "integer" + }, + "conditions": { + "description": "Conditions represent the latest available observations of the VirtualMCPServer's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "discoveredBackends": { + "description": "DiscoveredBackends lists discovered backend configurations from the MCPGroup", + "items": { + "description": "DiscoveredBackend represents a backend server discovered by vMCP runtime.\nThis type is shared with the Kubernetes operator CRD (VirtualMCPServer.Status.DiscoveredBackends).", + "properties": { + "authConfigRef": { + "description": "AuthConfigRef is the name of the discovered MCPExternalAuthConfig (if any)", + "type": "string" + }, + "authType": { + "description": "AuthType is the type of authentication configured", + "type": "string" + }, + "circuitBreakerState": { + "description": "CircuitBreakerState is the current circuit breaker state (closed, open, half-open).\nEmpty when circuit breaker is disabled or not configured.", + "enum": [ + "closed", + "open", + "half-open" + ], + "type": "string" + }, + "circuitLastChanged": { + "description": "CircuitLastChanged is the timestamp when the circuit breaker state last changed.\nEmpty when circuit breaker is disabled or has never changed state.", + "format": "date-time", + "type": "string" + }, + "consecutiveFailures": { + "description": "ConsecutiveFailures is the current count of consecutive health check failures.\nResets to 0 when the backend becomes healthy again.", + "type": "integer" + }, + "lastHealthCheck": { + "description": "LastHealthCheck is the timestamp of the last health check", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "Message provides additional information about the backend status", + "type": "string" + }, + "name": { + "description": "Name is the name of the backend MCPServer", + "type": "string" + }, + "status": { + "description": "Status is the current status of the backend (ready, degraded, unavailable, unauthenticated, unknown).\nUse BackendHealthStatus.ToCRDStatus() to populate this field.", + "type": "string" + }, + "url": { + "description": "URL is the URL of the backend MCPServer", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + }, + "message": { + "description": "Message provides additional information about the current phase", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration is the most recent generation observed for this VirtualMCPServer", + "format": "int64", + "type": "integer" + }, + "oidcConfigHash": { + "description": "OIDCConfigHash is the hash of the referenced MCPOIDCConfig spec for change detection.\nOnly populated when IncomingAuth.OIDCConfigRef is set.", + "type": "string" + }, + "phase": { + "default": "Pending", + "description": "Phase is the current phase of the VirtualMCPServer", + "enum": [ + "Pending", + "Ready", + "Degraded", + "Failed" + ], + "type": "string" + }, + "telemetryConfigHash": { + "description": "TelemetryConfigHash is the hash of the referenced MCPTelemetryConfig spec for change detection.\nOnly populated when TelemetryConfigRef is set.", + "type": "string" + }, + "url": { + "description": "URL is the URL where the Virtual MCP server can be accessed", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +} diff --git a/crdSchemas/virtualmcpserver_v1beta1.json b/crdSchemas/virtualmcpserver_v1beta1.json new file mode 100644 index 0000000..d8d0a19 --- /dev/null +++ b/crdSchemas/virtualmcpserver_v1beta1.json @@ -0,0 +1,2694 @@ +{ + "description": "VirtualMCPServer is the Schema for the virtualmcpservers API\nVirtualMCPServer aggregates multiple backend MCPServers into a unified endpoint", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "VirtualMCPServerSpec defines the desired state of VirtualMCPServer", + "properties": { + "authServerConfig": { + "description": "AuthServerConfig configures an embedded OAuth authorization server.\nWhen set, the vMCP server acts as an OIDC issuer, drives users through\nupstream IDPs, and issues ToolHive JWTs. The embedded AS becomes the\nIncomingAuth OIDC provider \u2014 its issuer must match IncomingAuth.OIDCConfigRef\nso that tokens it issues are accepted by the vMCP's incoming auth middleware.\nWhen nil, IncomingAuth uses an external IDP and behavior is unchanged.", + "properties": { + "authorizationEndpointBaseUrl": { + "description": "AuthorizationEndpointBaseURL overrides the base URL used for the authorization_endpoint\nin the OAuth discovery document. When set, the discovery document will advertise\n`{authorizationEndpointBaseUrl}/oauth/authorize` instead of `{issuer}/oauth/authorize`.\nAll other endpoints (token, registration, JWKS) remain derived from the issuer.\nThis is useful when the browser-facing authorization endpoint needs to be on a\ndifferent host than the issuer used for backend-to-backend calls.\nMust be a valid HTTPS URL (or HTTP for localhost) without query, fragment, or trailing slash.", + "pattern": "^https?://[^\\s?#]+[^/\\s?#]$", + "type": "string" + }, + "baselineClientScopes": { + "description": "BaselineClientScopes is a baseline set of OAuth 2.0 scopes guaranteed to be\nincluded in every client registration. The embedded auth server unions these\nscopes into the registered set returned by RFC 7591 Dynamic Client\nRegistration, so a client that narrows the `scope` field at /oauth/register\ncan still request the baseline scopes at /oauth/authorize. All values must\nbe present in the upstream-derived scopesSupported set; the auth server\nfails to start if any value is missing.\n\nSecurity: every client registered via /oauth/register will gain the\nability to request these scopes at /oauth/authorize, regardless of what\nthe client itself requested. Keep the baseline narrow (typically\n\"openid\" and \"offline_access\"). Adding a privileged scope here \u2014 e.g.\n\"admin:read\" \u2014 would grant it to every DCR-registered client, including\npublic clients like Claude Code, Cursor, and VS Code.", + "items": { + "minLength": 1, + "pattern": "^[\\x21\\x23-\\x5B\\x5D-\\x7E]+$", + "type": "string" + }, + "maxItems": 10, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "hmacSecretRefs": { + "description": "HMACSecretRefs references Kubernetes Secrets containing symmetric secrets for signing\nauthorization codes and refresh tokens (opaque tokens).\nCurrent secret must be at least 32 bytes and cryptographically random.\nSupports secret rotation via multiple entries (first is current, rest are for verification).\nIf not specified, an ephemeral secret will be auto-generated (development only -\nauth codes and refresh tokens will be invalid after restart).", + "items": { + "description": "SecretKeyRef is a reference to a key within a Secret", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "issuer": { + "description": "Issuer is the issuer identifier for this authorization server.\nThis will be included in the \"iss\" claim of issued tokens.\nMust be a valid HTTPS URL (or HTTP for localhost) without query, fragment, or trailing slash (per RFC 8414).", + "pattern": "^https?://[^\\s?#]+[^/\\s?#]$", + "type": "string" + }, + "primaryUpstreamProvider": { + "description": "PrimaryUpstreamProvider names the upstream IDP whose access token Cedar\nshould read claims from when authorising a request. Must match the name\nof one of the entries in UpstreamProviders. When empty, the controller\nauto-selects the first entry of UpstreamProviders.\n\nOnly meaningful on VirtualMCPServer, where multiple upstream providers\ncan be configured and Cedar needs to pick which token's claims to\nevaluate. The VirtualMCPServer controller validates this field against\nUpstreamProviders at admission and rejects unresolvable values.\n\nOn MCPServer and MCPRemoteProxy this field is structurally present (the\nEmbeddedAuthServerConfig struct is shared) but has no runtime effect:\nthose CRDs are restricted to a single upstream so there is no choice to\nmake. Setting it on those CRDs is silently ignored.", + "maxLength": 63, + "minLength": 1, + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "type": "string" + }, + "signingKeySecretRefs": { + "description": "SigningKeySecretRefs references Kubernetes Secrets containing signing keys for JWT operations.\nSupports key rotation by allowing multiple keys (oldest keys are used for verification only).\nIf not specified, an ephemeral signing key will be auto-generated (development only -\nJWTs will be invalid after restart).", + "items": { + "description": "SecretKeyRef is a reference to a key within a Secret", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "maxItems": 5, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "storage": { + "description": "Storage configures the storage backend for the embedded auth server.\nIf not specified, defaults to in-memory storage.", + "properties": { + "redis": { + "description": "Redis configures the Redis storage backend.\nRequired when type is \"redis\".", + "properties": { + "aclUserConfig": { + "description": "ACLUserConfig configures Redis ACL user authentication.", + "properties": { + "passwordSecretRef": { + "description": "PasswordSecretRef references a Secret containing the Redis ACL password.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "usernameSecretRef": { + "description": "UsernameSecretRef references a Secret containing the Redis ACL username.\nWhen omitted, connections use legacy password-only AUTH. Omit for managed\nRedis tiers that do not support ACL users (e.g. GCP Memorystore Basic/Standard\nHA, Azure Cache for Redis). Set for services that support ACL users (e.g. AWS\nElastiCache non-cluster with Redis 6+ RBAC).", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "passwordSecretRef" + ], + "type": "object", + "additionalProperties": false + }, + "addr": { + "description": "Addr is the Redis server address (host:port). Required for standalone and cluster modes.\nUse for managed Redis services that expose a single endpoint (GCP Memorystore basic tier,\nAWS ElastiCache without cluster mode, or cluster-mode services when clusterMode is true).\nMutually exclusive with sentinelConfig.", + "type": "string" + }, + "clusterMode": { + "description": "ClusterMode enables the Redis Cluster protocol. Set to true when addr points to a\nRedis Cluster discovery endpoint (e.g., GCP Memorystore Cluster, AWS ElastiCache\ncluster mode enabled). Requires addr to be set.", + "type": "boolean" + }, + "dialTimeout": { + "default": "5s", + "description": "DialTimeout is the timeout for establishing connections.\nFormat: Go duration string (e.g., \"5s\", \"1m\").", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "readTimeout": { + "default": "3s", + "description": "ReadTimeout is the timeout for socket reads.\nFormat: Go duration string (e.g., \"3s\", \"1m\").", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "sentinelConfig": { + "description": "SentinelConfig holds Redis Sentinel configuration.\nUse for self-managed Redis with Sentinel-based HA. Mutually exclusive with addr.", + "properties": { + "db": { + "default": 0, + "description": "DB is the Redis database number.", + "format": "int32", + "type": "integer" + }, + "masterName": { + "description": "MasterName is the name of the Redis master monitored by Sentinel.", + "type": "string" + }, + "sentinelAddrs": { + "description": "SentinelAddrs is a list of Sentinel host:port addresses.\nMutually exclusive with SentinelService.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "sentinelService": { + "description": "SentinelService enables automatic discovery from a Kubernetes Service.\nMutually exclusive with SentinelAddrs.", + "properties": { + "name": { + "description": "Name of the Sentinel Service.", + "type": "string" + }, + "namespace": { + "description": "Namespace of the Sentinel Service (defaults to same namespace).", + "type": "string" + }, + "port": { + "default": 26379, + "description": "Port of the Sentinel service.", + "format": "int32", + "type": "integer" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "masterName" + ], + "type": "object", + "additionalProperties": false + }, + "sentinelTls": { + "description": "SentinelTLS configures TLS for connections to Sentinel instances.\nOnly applies when sentinelConfig is set. Presence of this field enables TLS.", + "properties": { + "caCertSecretRef": { + "description": "CACertSecretRef references a Secret containing a PEM-encoded CA certificate\nfor verifying the server. When not specified, system root CAs are used.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "insecureSkipVerify": { + "description": "InsecureSkipVerify skips TLS certificate verification.\nUse when connecting to services with self-signed certificates.", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "tls": { + "description": "TLS configures TLS for connections to the Redis/Valkey master.\nPresence of this field enables TLS. Omit to use plaintext.", + "properties": { + "caCertSecretRef": { + "description": "CACertSecretRef references a Secret containing a PEM-encoded CA certificate\nfor verifying the server. When not specified, system root CAs are used.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "insecureSkipVerify": { + "description": "InsecureSkipVerify skips TLS certificate verification.\nUse when connecting to services with self-signed certificates.", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + }, + "writeTimeout": { + "default": "3s", + "description": "WriteTimeout is the timeout for socket writes.\nFormat: Go duration string (e.g., \"3s\", \"1m\").", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + } + }, + "required": [ + "aclUserConfig" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "exactly one of addr or sentinelConfig must be set", + "rule": "(has(self.addr) && self.addr.size() > 0) != has(self.sentinelConfig)" + }, + { + "message": "clusterMode requires addr to be set", + "rule": "!(has(self.clusterMode) && self.clusterMode) || (has(self.addr) && self.addr.size() > 0)" + } + ], + "additionalProperties": false + }, + "type": { + "default": "memory", + "description": "Type specifies the storage backend type.\nValid values: \"memory\" (default), \"redis\".", + "enum": [ + "memory", + "redis" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "tokenLifespans": { + "description": "TokenLifespans configures the duration that various tokens are valid.\nIf not specified, defaults are applied (access: 1h, refresh: 7d, authCode: 10m).", + "properties": { + "accessTokenLifespan": { + "description": "AccessTokenLifespan is the duration that access tokens are valid.\nFormat: Go duration string (e.g., \"1h\", \"30m\", \"24h\").\nIf empty, defaults to 1 hour.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "authCodeLifespan": { + "description": "AuthCodeLifespan is the duration that authorization codes are valid.\nFormat: Go duration string (e.g., \"10m\", \"5m\").\nIf empty, defaults to 10 minutes.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "refreshTokenLifespan": { + "description": "RefreshTokenLifespan is the duration that refresh tokens are valid.\nFormat: Go duration string (e.g., \"168h\", \"7d\" as \"168h\").\nIf empty, defaults to 7 days (168h).", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "upstreamProviders": { + "description": "UpstreamProviders configures connections to upstream Identity Providers.\nThe embedded auth server delegates authentication to these providers.\nMCPServer and MCPRemoteProxy support a single upstream; VirtualMCPServer supports multiple.", + "items": { + "description": "UpstreamProviderConfig defines configuration for an upstream Identity Provider.\n\nExactly one of OIDCConfig or OAuth2Config must be set and must match the\ndeclared Type: oidc-typed providers set OIDCConfig, oauth2-typed providers\nset OAuth2Config. The CEL rule below enforces the pairing at admission; the\nmatching Go-level check in validateUpstreamProvider provides defense-in-depth\nfor stored objects.\n\nThe rule is structured as a chain of equality checks ending in an explicit\n`false`, so adding a new UpstreamProviderType value without extending this\nrule fails admission instead of silently demanding the OAuth2 shape. When\nadding a new type, extend both this rule and validateUpstreamProvider.", + "properties": { + "name": { + "description": "Name uniquely identifies this upstream provider.\nUsed for routing decisions and session binding in multi-upstream scenarios.\nMust be lowercase alphanumeric with hyphens (DNS-label-like).", + "maxLength": 63, + "minLength": 1, + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "type": "string" + }, + "oauth2Config": { + "description": "OAuth2Config contains OAuth 2.0-specific configuration.\nRequired when Type is \"oauth2\", must be nil when Type is \"oidc\".", + "properties": { + "additionalAuthorizationParams": { + "additionalProperties": { + "type": "string" + }, + "description": "AdditionalAuthorizationParams are extra query parameters to include in\nauthorization requests sent to the upstream provider.\nThis is useful for providers that require custom parameters, such as\nGoogle's access_type=offline for obtaining refresh tokens.\nFramework-managed parameters (response_type, client_id, redirect_uri,\nscope, state, code_challenge, code_challenge_method, nonce) are not allowed.", + "maxProperties": 16, + "type": "object" + }, + "authorizationEndpoint": { + "description": "AuthorizationEndpoint is the URL for the OAuth authorization endpoint.", + "pattern": "^https?://.*$", + "type": "string" + }, + "clientId": { + "description": "ClientID is the OAuth 2.0 client identifier registered with the upstream IDP.\nMutually exclusive with DCRConfig: when DCRConfig is set, ClientID is obtained\nat runtime via RFC 7591 Dynamic Client Registration and must be left empty.", + "type": "string" + }, + "clientSecretRef": { + "description": "ClientSecretRef references a Kubernetes Secret containing the OAuth 2.0 client secret.\nOptional for public clients using PKCE instead of client secret.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "dcrConfig": { + "description": "DCRConfig enables RFC 7591 Dynamic Client Registration against the upstream\nauthorization server. When set, the client credentials are obtained at\nruntime rather than being pre-provisioned, and ClientID must be left empty.\nMutually exclusive with ClientID.", + "properties": { + "discoveryUrl": { + "description": "DiscoveryURL is the RFC 8414 / OIDC Discovery document URL. The resolver\nissues a single GET against this URL (no well-known-path fallback) and\nreads registration_endpoint, authorization_endpoint, token_endpoint,\ntoken_endpoint_auth_methods_supported, and scopes_supported from the\nresponse.\nMutually exclusive with RegistrationEndpoint.\nHTTPS is required because the registration endpoint resolved from this\ndocument carries the initial access token and the issued client_secret\n(RFC 7591 \u00a73, RFC 8414 \u00a73). MaxLength is a defensive size cap (etcd\nobject budget, regex evaluation cost) and matches the conventional URL\nlength cap.", + "maxLength": 2048, + "pattern": "^https://[^\\s?#]+[^/\\s?#]$", + "type": "string" + }, + "initialAccessTokenRef": { + "description": "InitialAccessTokenRef is an optional reference to a Kubernetes Secret\ncarrying an RFC 7591 \u00a73 initial access token. When set, the resolver\npresents the token value as a Bearer credential on the registration\nrequest. Mirrors the ClientSecretRef pattern.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "registrationEndpoint": { + "description": "RegistrationEndpoint is the RFC 7591 registration endpoint URL used\ndirectly, bypassing discovery. When using this field, the caller is\nexpected to also supply AuthorizationEndpoint, TokenEndpoint, and an\nexplicit Scopes list on the parent OAuth2UpstreamConfig.\nMutually exclusive with DiscoveryURL.\nHTTPS is required because the registration endpoint carries the initial\naccess token and the issued client_secret (RFC 7591 \u00a73, RFC 8414 \u00a73).\nMaxLength is a defensive size cap (etcd object budget, regex evaluation\ncost) and matches the conventional URL length cap.", + "maxLength": 2048, + "pattern": "^https://[^\\s?#]+[^/\\s?#]$", + "type": "string" + }, + "softwareId": { + "description": "SoftwareID is the RFC 7591 \"software_id\" registration metadata value,\nidentifying the client software independent of any particular\nregistration instance. Typically a UUID or short identifier.", + "maxLength": 255, + "type": "string" + }, + "softwareStatement": { + "description": "SoftwareStatement is the RFC 7591 \"software_statement\" JWT asserting\nmetadata about the client software, signed by a party the authorization\nserver trusts.\n\nStored inline on the CR. The JWT is signed but not encrypted, so its\ncontents are visible to anyone with get/list/watch on this resource and\nappear in etcd backups in plaintext. Treat the value as non-confidential\n(signed attestation, not a secret). Operators that rotate software\nstatements like bearer credentials should keep them at the authorization\nserver side and rely on the registration endpoint's initial access\ntoken (see InitialAccessTokenRef) instead of placing them on the CR.\n\nBounded to 16384 characters as a defensive size cap (etcd object\nbudget, regex evaluation cost). Real-world signed statements with\nembedded x5c certificate chains, JWKS keys, or OIDC-Federation\ntrust-framework metadata routinely exceed 4 KB.", + "maxLength": 16384, + "type": "string" + } + }, + "type": "object", + "x-kubernetes-validations": [ + { + "message": "exactly one of discoveryUrl or registrationEndpoint must be set", + "rule": "has(self.discoveryUrl) != has(self.registrationEndpoint)" + } + ], + "additionalProperties": false + }, + "identityFromToken": { + "description": "IdentityFromToken extracts user identity (subject, name, email) directly\nfrom the OAuth2 token-endpoint response body using gjson dot-notation paths.\nWhen set, the embedded auth server skips the userinfo HTTP call entirely\nand resolves identity from the token response. See IdentityFromTokenConfig\nfor trust-model and uniqueness considerations.", + "properties": { + "emailPath": { + "description": "EmailPath is the dot-notation path to the email address field in the token response.\nIf not specified or if the path does not resolve to a string, the email is omitted.\nOmit the field entirely rather than setting it to an empty string.", + "maxLength": 256, + "minLength": 1, + "type": "string" + }, + "namePath": { + "description": "NamePath is the dot-notation path to the display name field in the token response.\nIf not specified or if the path does not resolve to a string, the display name is omitted.\nOmit the field entirely rather than setting it to an empty string.", + "maxLength": 256, + "minLength": 1, + "type": "string" + }, + "subjectPath": { + "description": "SubjectPath is the dot-notation path to the subject (user ID) field in the token response.\nWarning: claims read from the token response are trusted only via TLS, not\ncryptographically verified; prefer OIDC ID tokens when verifiable claims are required.\nExample: \"authed_user.id\" for Slack (top-level token-response field). For providers\nwhose token response embeds the access token as a JWT (e.g. Snowflake), use the\n\"@upstreamjwt\" modifier to decode the payload, e.g. \"access_token|@upstreamjwt|sub\".\nThe \"@upstreamjwt\" modifier performs no signature verification either.", + "maxLength": 256, + "minLength": 1, + "type": "string" + } + }, + "required": [ + "subjectPath" + ], + "type": "object", + "additionalProperties": false + }, + "redirectUri": { + "description": "RedirectURI is the callback URL where the upstream IDP will redirect after authentication.\nWhen not specified, defaults to `{resourceUrl}/oauth/callback` where `resourceUrl` is the\nURL associated with the resource (e.g., MCPServer or vMCP) using this config.", + "type": "string" + }, + "scopes": { + "description": "Scopes are the OAuth scopes to request from the upstream IDP.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "tokenEndpoint": { + "description": "TokenEndpoint is the URL for the OAuth token endpoint.", + "pattern": "^https?://.*$", + "type": "string" + }, + "tokenResponseMapping": { + "description": "TokenResponseMapping configures custom field extraction from non-standard token responses.\nSome OAuth providers (e.g., GovSlack) nest token fields under non-standard paths\ninstead of returning them at the top level. When set, ToolHive performs the token\nexchange HTTP call directly and extracts fields using the configured dot-notation paths.\nIf nil, standard OAuth 2.0 token response parsing is used.\nFor extracting user identity from the token response, see IdentityFromToken.", + "properties": { + "accessTokenPath": { + "description": "AccessTokenPath is the dot-notation path to the access token in the response.\nExample: \"authed_user.access_token\"", + "minLength": 1, + "type": "string" + }, + "expiresInPath": { + "description": "ExpiresInPath is the dot-notation path to the expires_in value (in seconds).\nIf not specified, defaults to \"expires_in\".", + "type": "string" + }, + "refreshTokenPath": { + "description": "RefreshTokenPath is the dot-notation path to the refresh token in the response.\nIf not specified, defaults to \"refresh_token\".", + "type": "string" + }, + "scopePath": { + "description": "ScopePath is the dot-notation path to the scope string in the response.\nIf not specified, defaults to \"scope\".", + "type": "string" + } + }, + "required": [ + "accessTokenPath" + ], + "type": "object", + "additionalProperties": false + }, + "userInfo": { + "description": "UserInfo contains configuration for fetching user information from the upstream provider.\nWhen omitted and IdentityFromToken is also unset, the embedded auth server runs in\nsynthesis mode for this upstream: a non-PII subject derived from the access token, no\nName/Email. Use this shape for upstreams with no userinfo surface and no identity in\nthe token response (e.g., MCP authorization servers per the MCP spec). When\nIdentityFromToken is set instead, identity is resolved from the token response body\n(e.g., Snowflake's \"username\" field, Slack's \"authed_user.id\"); the userinfo HTTP call\nis skipped entirely.", + "properties": { + "additionalHeaders": { + "additionalProperties": { + "type": "string" + }, + "description": "AdditionalHeaders contains extra headers to include in the userinfo request.\nUseful for providers that require specific headers (e.g., GitHub's Accept header).", + "type": "object" + }, + "endpointUrl": { + "description": "EndpointURL is the URL of the userinfo endpoint.", + "pattern": "^https?://.*$", + "type": "string" + }, + "fieldMapping": { + "description": "FieldMapping contains custom field mapping configuration for non-standard providers.\nIf nil, standard OIDC field names are used (\"sub\", \"name\", \"email\").", + "properties": { + "emailFields": { + "description": "EmailFields is an ordered list of field names to try for the email address.\nThe first non-empty value found will be used.\nDefault: [\"email\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "nameFields": { + "description": "NameFields is an ordered list of field names to try for the display name.\nThe first non-empty value found will be used.\nDefault: [\"name\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "subjectFields": { + "description": "SubjectFields is an ordered list of field names to try for the user ID.\nThe first non-empty value found will be used.\nDefault: [\"sub\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "type": "object", + "additionalProperties": false + }, + "httpMethod": { + "description": "HTTPMethod is the HTTP method to use for the userinfo request.\nIf not specified, defaults to GET.", + "enum": [ + "GET", + "POST" + ], + "type": "string" + } + }, + "required": [ + "endpointUrl" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "authorizationEndpoint", + "tokenEndpoint" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "exactly one of clientId or dcrConfig must be set", + "rule": "(has(self.clientId) && size(self.clientId) > 0) ? !has(self.dcrConfig) : has(self.dcrConfig)" + }, + { + "message": "clientSecretRef must not be set when dcrConfig is set; the client_secret is obtained at runtime via Dynamic Client Registration", + "rule": "!(has(self.dcrConfig) && has(self.clientSecretRef))" + } + ], + "additionalProperties": false + }, + "oidcConfig": { + "description": "OIDCConfig contains OIDC-specific configuration.\nRequired when Type is \"oidc\", must be nil when Type is \"oauth2\".", + "properties": { + "additionalAuthorizationParams": { + "additionalProperties": { + "type": "string" + }, + "description": "AdditionalAuthorizationParams are extra query parameters to include in\nauthorization requests sent to the upstream provider.\nThis is useful for providers that require custom parameters, such as\nGoogle's access_type=offline for obtaining refresh tokens.\nNote: when using access_type=offline, also set explicit scopes to avoid\nthe default offline_access scope being sent alongside it.\nFramework-managed parameters (response_type, client_id, redirect_uri,\nscope, state, code_challenge, code_challenge_method, nonce) are not allowed.", + "maxProperties": 16, + "type": "object" + }, + "clientId": { + "description": "ClientID is the OAuth 2.0 client identifier registered with the upstream IDP.", + "type": "string" + }, + "clientSecretRef": { + "description": "ClientSecretRef references a Kubernetes Secret containing the OAuth 2.0 client secret.\nOptional for public clients using PKCE instead of client secret.", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "issuerUrl": { + "description": "IssuerURL is the OIDC issuer URL for automatic endpoint discovery.\nMust be a valid HTTPS URL.", + "pattern": "^https://.*$", + "type": "string" + }, + "redirectUri": { + "description": "RedirectURI is the callback URL where the upstream IDP will redirect after authentication.\nWhen not specified, defaults to `{resourceUrl}/oauth/callback` where `resourceUrl` is the\nURL associated with the resource (e.g., MCPServer or vMCP) using this config.", + "type": "string" + }, + "scopes": { + "description": "Scopes are the OAuth scopes to request from the upstream IDP.\nIf not specified, defaults to [\"openid\", \"offline_access\"].\nWhen using additionalAuthorizationParams with provider-specific refresh token\nmechanisms (e.g., Google's access_type=offline), set explicit scopes to avoid\nsending both offline_access and the provider-specific parameter.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "userInfoOverride": { + "description": "UserInfoOverride allows customizing UserInfo fetching behavior for OIDC providers.\nBy default, the UserInfo endpoint is discovered automatically via OIDC discovery.\nUse this to override the endpoint URL, HTTP method, or field mappings for providers\nthat return non-standard claim names in their UserInfo response.", + "properties": { + "additionalHeaders": { + "additionalProperties": { + "type": "string" + }, + "description": "AdditionalHeaders contains extra headers to include in the userinfo request.\nUseful for providers that require specific headers (e.g., GitHub's Accept header).", + "type": "object" + }, + "endpointUrl": { + "description": "EndpointURL is the URL of the userinfo endpoint.", + "pattern": "^https?://.*$", + "type": "string" + }, + "fieldMapping": { + "description": "FieldMapping contains custom field mapping configuration for non-standard providers.\nIf nil, standard OIDC field names are used (\"sub\", \"name\", \"email\").", + "properties": { + "emailFields": { + "description": "EmailFields is an ordered list of field names to try for the email address.\nThe first non-empty value found will be used.\nDefault: [\"email\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "nameFields": { + "description": "NameFields is an ordered list of field names to try for the display name.\nThe first non-empty value found will be used.\nDefault: [\"name\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "subjectFields": { + "description": "SubjectFields is an ordered list of field names to try for the user ID.\nThe first non-empty value found will be used.\nDefault: [\"sub\"]", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "type": "object", + "additionalProperties": false + }, + "httpMethod": { + "description": "HTTPMethod is the HTTP method to use for the userinfo request.\nIf not specified, defaults to GET.", + "enum": [ + "GET", + "POST" + ], + "type": "string" + } + }, + "required": [ + "endpointUrl" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "clientId", + "issuerUrl" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type specifies the provider type: \"oidc\" or \"oauth2\"", + "enum": [ + "oidc", + "oauth2" + ], + "type": "string" + } + }, + "required": [ + "name", + "type" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "type must be 'oidc' or 'oauth2'; oidcConfig must be set when type is 'oidc' and oauth2Config must be set when type is 'oauth2' (and the other must not be set)", + "rule": "self.type == 'oidc' ? (has(self.oidcConfig) && !has(self.oauth2Config)) : self.type == 'oauth2' ? (has(self.oauth2Config) && !has(self.oidcConfig)) : false" + } + ], + "additionalProperties": false + }, + "minItems": 1, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "required": [ + "issuer", + "upstreamProviders" + ], + "type": "object", + "additionalProperties": false + }, + "config": { + "description": "Config is the Virtual MCP server configuration.\nThe audit config from here is also supported, but not required.", + "properties": { + "aggregation": { + "description": "Aggregation defines tool aggregation and conflict resolution strategies.\nSupports ToolConfigRef for Kubernetes-native MCPToolConfig resource references.", + "properties": { + "conflictResolution": { + "default": "prefix", + "description": "ConflictResolution defines the strategy for resolving tool name conflicts.\n- prefix: Automatically prefix tool names with workload identifier\n- priority: First workload in priority order wins\n- manual: Explicitly define overrides for all conflicts", + "enum": [ + "prefix", + "priority", + "manual" + ], + "type": "string" + }, + "conflictResolutionConfig": { + "description": "ConflictResolutionConfig provides configuration for the chosen strategy.", + "properties": { + "prefixFormat": { + "default": "{workload}_", + "description": "PrefixFormat defines the prefix format for the \"prefix\" strategy.\nSupports placeholders: {workload}, {workload}_, {workload}.", + "type": "string" + }, + "priorityOrder": { + "description": "PriorityOrder defines the workload priority order for the \"priority\" strategy.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "additionalProperties": false + }, + "excludeAllTools": { + "description": "ExcludeAllTools hides all backend tools from MCP clients when true.\nHidden tools are NOT advertised in tools/list responses, but they ARE\navailable in the routing table for composite tools to use.\nThis enables the use case where you want to hide raw backend tools from\ndirect client access while exposing curated composite tool workflows.", + "type": "boolean" + }, + "tools": { + "description": "Tools defines per-workload tool filtering and overrides.", + "items": { + "description": "WorkloadToolConfig defines tool filtering and overrides for a specific workload.", + "properties": { + "excludeAll": { + "description": "ExcludeAll hides all tools from this workload from MCP clients when true.\nHidden tools are NOT advertised in tools/list responses, but they ARE\navailable in the routing table for composite tools to use.\nThis enables the use case where you want to hide raw backend tools from\ndirect client access while exposing curated composite tool workflows.", + "type": "boolean" + }, + "filter": { + "description": "Filter is an allow-list of tool names to advertise to MCP clients.\nTools NOT in this list are hidden from clients (not in tools/list response)\nbut remain available in the routing table for composite tools to use.\nThis enables selective exposure of backend tools while allowing composite\nworkflows to orchestrate all backend capabilities.\nOnly used if ToolConfigRef is not specified.", + "items": { + "type": "string" + }, + "type": "array" + }, + "overrides": { + "additionalProperties": { + "description": "ToolOverride defines tool name, description, and annotation overrides.", + "properties": { + "annotations": { + "description": "Annotations overrides specific tool annotation fields.\nOnly specified fields are overridden; others pass through from the backend.", + "properties": { + "destructiveHint": { + "description": "DestructiveHint overrides the destructive hint annotation.", + "type": "boolean" + }, + "idempotentHint": { + "description": "IdempotentHint overrides the idempotent hint annotation.", + "type": "boolean" + }, + "openWorldHint": { + "description": "OpenWorldHint overrides the open-world hint annotation.", + "type": "boolean" + }, + "readOnlyHint": { + "description": "ReadOnlyHint overrides the read-only hint annotation.", + "type": "boolean" + }, + "title": { + "description": "Title overrides the human-readable title annotation.", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "description": { + "description": "Description is the new tool description.", + "type": "string" + }, + "name": { + "description": "Name is the new tool name (for renaming).", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "description": "Overrides is an inline map of tool overrides for renaming and description changes.\nOverrides are applied to tools before conflict resolution and affect both\nadvertising and routing (the overridden name is used everywhere).\nOnly used if ToolConfigRef is not specified.", + "type": "object" + }, + "toolConfigRef": { + "description": "ToolConfigRef references an MCPToolConfig resource for tool filtering and renaming.\nIf specified, Filter and Overrides are ignored.\nOnly used when running in Kubernetes with the operator.", + "properties": { + "name": { + "description": "Name is the name of the MCPToolConfig resource in the same namespace.", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "workload": { + "description": "Workload is the name of the backend MCPServer workload.", + "type": "string" + } + }, + "required": [ + "workload" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + } + }, + "type": "object", + "additionalProperties": false + }, + "audit": { + "description": "Audit configures audit logging for the Virtual MCP server.\nWhen present, audit logs include MCP protocol operations.\nSee audit.Config for available configuration options.", + "properties": { + "component": { + "description": "Component is the component name to use in audit events.", + "type": "string" + }, + "detectApplicationErrors": { + "default": true, + "description": "DetectApplicationErrors controls whether the audit middleware inspects\nJSON-RPC response bodies for application-level errors when the HTTP\nstatus code indicates success (2xx). When enabled, a small prefix of\nthe response body is buffered to detect JSON-RPC error fields,\nindependent of the IncludeResponseData setting.", + "type": "boolean" + }, + "enabled": { + "default": false, + "description": "Enabled controls whether audit logging is enabled.\nWhen true, enables audit logging with the configured options.", + "type": "boolean" + }, + "eventTypes": { + "description": "EventTypes specifies which event types to audit. If empty, all events are audited.", + "items": { + "type": "string" + }, + "type": "array" + }, + "excludeEventTypes": { + "description": "ExcludeEventTypes specifies which event types to exclude from auditing.\nThis takes precedence over EventTypes.", + "items": { + "type": "string" + }, + "type": "array" + }, + "includeRequestData": { + "default": false, + "description": "IncludeRequestData determines whether to include request data in audit logs.", + "type": "boolean" + }, + "includeResponseData": { + "default": false, + "description": "IncludeResponseData determines whether to include response data in audit logs.", + "type": "boolean" + }, + "logFile": { + "description": "LogFile specifies the file path for audit logs. If empty, logs to stdout.", + "type": "string" + }, + "maxDataSize": { + "default": 1024, + "description": "MaxDataSize limits the size of request/response data included in audit logs (in bytes).", + "type": "integer" + } + }, + "type": "object", + "additionalProperties": false + }, + "backends": { + "description": "Backends defines pre-configured backend servers for static mode.\nWhen OutgoingAuth.Source is \"inline\", this field contains the full list of backend\nservers with their URLs and transport types, eliminating the need for K8s API access.\nWhen OutgoingAuth.Source is \"discovered\", this field is empty and backends are\ndiscovered at runtime via Kubernetes API.", + "items": { + "description": "StaticBackendConfig defines a pre-configured backend server for static mode.\nThis allows vMCP to operate without Kubernetes API access by embedding all backend\ninformation directly in the configuration.", + "properties": { + "caBundlePath": { + "description": "CABundlePath is the file path to a custom CA certificate bundle for TLS verification.\nOnly valid when Type is \"entry\". The operator mounts CA bundles at\n/etc/toolhive/ca-bundles//ca.crt.", + "type": "string" + }, + "metadata": { + "additionalProperties": { + "type": "string" + }, + "description": "Metadata is a custom key-value map for storing additional backend information\nsuch as labels, tags, or other arbitrary data (e.g., \"env\": \"prod\", \"region\": \"us-east-1\").\nThis is NOT Kubernetes ObjectMeta - it's a simple string map for user-defined metadata.\nReserved keys: \"group\" is automatically set by vMCP and any user-provided value will be overridden.", + "type": "object" + }, + "name": { + "description": "Name is the backend identifier.\nMust match the backend name from the MCPGroup for auth config resolution.", + "type": "string" + }, + "transport": { + "description": "Transport is the MCP transport protocol: \"sse\" or \"streamable-http\"\nOnly network transports supported by vMCP client are allowed.", + "enum": [ + "sse", + "streamable-http" + ], + "type": "string" + }, + "type": { + "description": "Type is the backend workload type: \"entry\" for MCPServerEntry backends, or empty\nfor container/proxy backends. Entry backends connect directly to remote MCP servers.", + "enum": [ + "entry", + "" + ], + "type": "string" + }, + "url": { + "description": "URL is the backend's MCP server base URL.", + "pattern": "^https?://", + "type": "string" + } + }, + "required": [ + "name", + "transport", + "url" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + }, + "compositeToolRefs": { + "description": "CompositeToolRefs references VirtualMCPCompositeToolDefinition resources\nfor complex, reusable workflows. Only applicable when running in Kubernetes.\nReferenced resources must be in the same namespace as the VirtualMCPServer.", + "items": { + "description": "CompositeToolRef defines a reference to a VirtualMCPCompositeToolDefinition resource.\nThe referenced resource must be in the same namespace as the VirtualMCPServer.", + "properties": { + "name": { + "description": "Name is the name of the VirtualMCPCompositeToolDefinition resource in the same namespace.", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + }, + "compositeTools": { + "description": "CompositeTools defines inline composite tool workflows.\nFull workflow definitions are embedded in the configuration.\nFor Kubernetes, complex workflows can also reference VirtualMCPCompositeToolDefinition CRDs.", + "items": { + "description": "CompositeToolConfig defines a composite tool workflow.\nThis matches the YAML structure from the proposal (lines 173-255).", + "properties": { + "description": { + "description": "Description describes what the workflow does.", + "type": "string" + }, + "name": { + "description": "Name is the workflow name (unique identifier).", + "type": "string" + }, + "output": { + "description": "Output defines the structured output schema for this workflow.\nIf not specified, the workflow returns the last step's output (backward compatible).", + "properties": { + "properties": { + "additionalProperties": { + "description": "OutputProperty defines a single output property.\nFor non-object types, Value is required.\nFor object types, either Value or Properties must be specified (but not both).", + "properties": { + "default": { + "description": "Default is the fallback value if template expansion fails.\nType coercion is applied to match the declared Type.", + "x-kubernetes-preserve-unknown-fields": true + }, + "description": { + "description": "Description is a human-readable description exposed to clients and models", + "type": "string" + }, + "properties": { + "description": "Properties defines nested properties for object types.\nEach nested property has full metadata (type, description, value/properties).", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "type": { + "description": "Type is the JSON Schema type: \"string\", \"integer\", \"number\", \"boolean\", \"object\", \"array\"", + "enum": [ + "string", + "integer", + "number", + "boolean", + "object", + "array" + ], + "type": "string" + }, + "value": { + "description": "Value is a template string for constructing the runtime value.\nFor object types, this can be a JSON string that will be deserialized.\nSupports template syntax: {{.steps.step_id.output.field}}, {{.params.param_name}}", + "type": "string" + }, + "additionalProperties": false + }, + "required": [ + "type" + ], + "type": "object", + "additionalProperties": false + }, + "description": "Properties defines the output properties.\nMap key is the property name, value is the property definition.", + "type": "object" + }, + "required": { + "description": "Required lists property names that must be present in the output.", + "items": { + "type": "string" + }, + "type": "array" + }, + "additionalProperties": false + }, + "required": [ + "properties" + ], + "type": "object", + "additionalProperties": false + }, + "parameters": { + "description": "Parameters defines input parameter schema in JSON Schema format.\nShould be a JSON Schema object with \"type\": \"object\" and \"properties\".\nExample:\n {\n \"type\": \"object\",\n \"properties\": {\n \"param1\": {\"type\": \"string\", \"default\": \"value\"},\n \"param2\": {\"type\": \"integer\"}\n },\n \"required\": [\"param2\"]\n }\n\nWe use json.Map rather than a typed struct because JSON Schema is highly\nflexible with many optional fields (default, enum, minimum, maximum, pattern,\nitems, additionalProperties, oneOf, anyOf, allOf, etc.). Using json.Map\nallows full JSON Schema compatibility without needing to define every possible\nfield, and matches how the MCP SDK handles inputSchema.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "steps": { + "description": "Steps are the workflow steps to execute.", + "items": { + "description": "WorkflowStepConfig defines a single workflow step.\nThis matches the proposal's step configuration (lines 180-255).", + "properties": { + "arguments": { + "description": "Arguments is a map of argument values with template expansion support.\nSupports Go template syntax with .params and .steps for string values.\nNon-string values (integers, booleans, arrays, objects) are passed as-is.\nNote: the templating is only supported on the first level of the key-value pairs.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "collection": { + "description": "Collection is a Go template expression that resolves to a JSON array or a slice.\nOnly used when Type is \"forEach\".", + "type": "string" + }, + "condition": { + "description": "Condition is a template expression that determines if the step should execute", + "type": "string" + }, + "defaultResults": { + "description": "DefaultResults provides fallback output values when this step is skipped\n(due to condition evaluating to false) or fails (when onError.action is \"continue\").\nEach key corresponds to an output field name referenced by downstream steps.\nRequired if the step may be skipped AND downstream steps reference this step's output.", + "x-kubernetes-preserve-unknown-fields": true + }, + "dependsOn": { + "description": "DependsOn lists step IDs that must complete before this step", + "items": { + "type": "string" + }, + "type": "array" + }, + "id": { + "description": "ID is the unique identifier for this step.", + "type": "string" + }, + "itemVar": { + "description": "ItemVar is the variable name used to reference the current item in forEach templates.\nDefaults to \"item\" if not specified.\nOnly used when Type is \"forEach\".", + "type": "string" + }, + "maxIterations": { + "description": "MaxIterations limits the number of items that can be iterated over.\nDefaults to 100, hard cap at 1000.\nOnly used when Type is \"forEach\".", + "type": "integer" + }, + "maxParallel": { + "description": "MaxParallel limits the number of concurrent iterations in a forEach step.\nDefaults to the DAG executor's maxParallel (10).\nOnly used when Type is \"forEach\".", + "type": "integer" + }, + "message": { + "description": "Message is the elicitation message\nOnly used when Type is \"elicitation\"", + "type": "string" + }, + "onCancel": { + "description": "OnCancel defines the action to take when the user cancels/dismisses the elicitation\nOnly used when Type is \"elicitation\"", + "properties": { + "action": { + "default": "abort", + "description": "Action defines the action to take when the user declines or cancels\n- skip_remaining: Skip remaining steps in the workflow\n- abort: Abort the entire workflow execution\n- continue: Continue to the next step", + "enum": [ + "skip_remaining", + "abort", + "continue" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "onDecline": { + "description": "OnDecline defines the action to take when the user explicitly declines the elicitation\nOnly used when Type is \"elicitation\"", + "properties": { + "action": { + "default": "abort", + "description": "Action defines the action to take when the user declines or cancels\n- skip_remaining: Skip remaining steps in the workflow\n- abort: Abort the entire workflow execution\n- continue: Continue to the next step", + "enum": [ + "skip_remaining", + "abort", + "continue" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "onError": { + "description": "OnError defines error handling behavior", + "properties": { + "action": { + "default": "abort", + "description": "Action defines the action to take on error", + "enum": [ + "abort", + "continue", + "retry" + ], + "type": "string" + }, + "retryCount": { + "description": "RetryCount is the maximum number of retries\nOnly used when Action is \"retry\"", + "type": "integer" + }, + "retryDelay": { + "description": "RetryDelay is the delay between retry attempts\nOnly used when Action is \"retry\"", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "schema": { + "description": "Schema defines the expected response schema for elicitation", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "step": { + "description": "InnerStep defines the step to execute for each item in the collection.\nOnly used when Type is \"forEach\". Only tool-type inner steps are supported.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "timeout": { + "description": "Timeout is the maximum execution time for this step", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "tool": { + "description": "Tool is the tool to call (format: \"workload.tool_name\")\nOnly used when Type is \"tool\"", + "type": "string" + }, + "type": { + "default": "tool", + "description": "Type is the step type (tool, elicitation, etc.)", + "enum": [ + "tool", + "elicitation", + "forEach" + ], + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + }, + "timeout": { + "description": "Timeout is the maximum workflow execution time.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + } + }, + "required": [ + "name", + "steps" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + }, + "groupRef": { + "description": "Group references an existing MCPGroup that defines backend workloads.\nIn standalone CLI mode, this is set from the YAML config file.\nIn Kubernetes, the operator populates this from spec.groupRef during conversion.", + "type": "string" + }, + "incomingAuth": { + "description": "IncomingAuth configures how clients authenticate to the virtual MCP server.\nWhen using the Kubernetes operator, this is populated by the converter from\nVirtualMCPServerSpec.IncomingAuth and any values set here will be superseded.", + "properties": { + "authz": { + "description": "Authz contains authorization configuration (optional).", + "properties": { + "entitiesJson": { + "description": "EntitiesJSON is a JSON string representing Cedar entities. Required for\nenterprise policies that rely on transitive relationships (e.g.\n`ClaimGroup \u2192 PlatformRole`) \u2014 without it the Cedar authorizer is\nconstructed with an empty entity store and `in` checks against absent\nentities silently evaluate to false. Defaults to \"[]\" when empty.", + "type": "string" + }, + "groupClaimName": { + "description": "GroupClaimName is the JWT claim key that contains group membership for\nthe principal. When set, takes priority over the well-known defaults\n(\"groups\", \"roles\", \"cognito:groups\"). Use this for IDPs that place\ngroups under a URI-style claim (e.g. \"https://example.com/groups\").\nWhen empty, only the well-known claim names are checked.", + "type": "string" + }, + "groupEntityType": { + "description": "GroupEntityType is the Cedar entity type name used for principal parent\nUIDs synthesised from JWT group/role claims. Defaults to \"THVGroup\" when\nempty. Must match the entity type used in EntitiesJSON for transitive\n`in` checks to resolve. Namespaced names (`Foo::Bar`) are not yet supported.", + "type": "string" + }, + "policies": { + "description": "Policies contains Cedar policy definitions (when Type = \"cedar\").", + "items": { + "type": "string" + }, + "type": "array" + }, + "primaryUpstreamProvider": { + "description": "PrimaryUpstreamProvider names the upstream IDP provider whose access\ntoken should be used as the source of JWT claims for Cedar evaluation.\nWhen empty, claims from the ToolHive-issued token are used.\nMust match an upstream provider name configured in the embedded auth server\n(e.g. \"default\", \"github\"). Only relevant when the embedded auth server is active.", + "type": "string" + }, + "roleClaimName": { + "description": "RoleClaimName is the JWT claim key that contains role membership for the\nprincipal. When set, the claim is extracted separately from GroupClaimName\nand both are mapped to the configured group entity type. When empty, no\nrole extraction is performed.", + "type": "string" + }, + "type": { + "description": "Type is the authz type: \"cedar\", \"none\"", + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "additionalProperties": false + }, + "oidc": { + "description": "OIDC contains OIDC configuration (when Type = \"oidc\").", + "properties": { + "audience": { + "description": "Audience is the required token audience.", + "type": "string" + }, + "clientId": { + "description": "ClientID is the OAuth client ID.", + "type": "string" + }, + "clientSecretEnv": { + "description": "ClientSecretEnv is the name of the environment variable containing the client secret.\nThis is the secure way to reference secrets - the actual secret value is never stored\nin configuration files, only the environment variable name.\nThe secret value will be resolved from this environment variable at runtime.", + "type": "string" + }, + "insecureAllowHttp": { + "description": "InsecureAllowHTTP allows HTTP (non-HTTPS) OIDC issuers for development/testing\nWARNING: This is insecure and should NEVER be used in production", + "type": "boolean" + }, + "introspectionUrl": { + "description": "IntrospectionURL is the token introspection endpoint URL (RFC 7662).\nWhen set, enables token introspection for opaque (non-JWT) tokens.", + "type": "string" + }, + "issuer": { + "description": "Issuer is the OIDC issuer URL.", + "pattern": "^https?://", + "type": "string" + }, + "jwksAllowPrivateIp": { + "description": "JwksAllowPrivateIP allows OIDC discovery and JWKS fetches to private IP addresses.\nEnable when the embedded auth server runs on a loopback address and\nthe OIDC middleware needs to fetch its JWKS from that address.\nUse with caution - only enable for trusted internal IDPs or testing.", + "type": "boolean" + }, + "jwksUrl": { + "description": "JWKSURL is the explicit JWKS endpoint URL.\nWhen set, skips OIDC discovery and fetches the JWKS directly from this URL.\nThis is useful when the OIDC issuer does not serve a /.well-known/openid-configuration.", + "type": "string" + }, + "protectedResourceAllowPrivateIp": { + "description": "ProtectedResourceAllowPrivateIP allows protected resource endpoint on private IP addresses\nUse with caution - only enable for trusted internal IDPs or testing", + "type": "boolean" + }, + "resource": { + "description": "Resource is the OAuth 2.0 resource indicator (RFC 8707).\nUsed in WWW-Authenticate header and OAuth discovery metadata (RFC 9728).\nIf not specified, defaults to Audience.", + "type": "string" + }, + "scopes": { + "description": "Scopes are the required OAuth scopes.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "audience", + "clientId", + "issuer" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type is the auth type: \"oidc\", \"local\", \"anonymous\"", + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "additionalProperties": false + }, + "metadata": { + "additionalProperties": { + "type": "string" + }, + "description": "Metadata stores additional configuration metadata.", + "type": "object" + }, + "name": { + "description": "Name is the virtual MCP server name.", + "type": "string" + }, + "operational": { + "description": "Operational configures operational settings.", + "properties": { + "failureHandling": { + "description": "FailureHandling configures failure handling behavior.", + "properties": { + "circuitBreaker": { + "description": "CircuitBreaker configures circuit breaker behavior.", + "properties": { + "enabled": { + "default": false, + "description": "Enabled controls whether circuit breaker is enabled.", + "type": "boolean" + }, + "failureThreshold": { + "default": 5, + "description": "FailureThreshold is the number of failures before opening the circuit.\nMust be >= 1.", + "minimum": 1, + "type": "integer" + }, + "timeout": { + "default": "60s", + "description": "Timeout is the duration to wait before attempting to close the circuit.\nMust be >= 1s to prevent thrashing.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string", + "x-kubernetes-validations": [ + { + "message": "timeout must be >= 1s", + "rule": "self == '' || duration(self) >= duration('1s')" + } + ] + } + }, + "type": "object", + "additionalProperties": false + }, + "healthCheckInterval": { + "default": "30s", + "description": "HealthCheckInterval is the interval between health checks.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "healthCheckTimeout": { + "default": "10s", + "description": "HealthCheckTimeout is the maximum duration for a single health check operation.\nShould be less than HealthCheckInterval to prevent checks from queuing up.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "partialFailureMode": { + "default": "fail", + "description": "PartialFailureMode defines behavior when some backends are unavailable.\n- fail: Fail entire request if any backend is unavailable\n- best_effort: Continue with available backends", + "enum": [ + "fail", + "best_effort" + ], + "type": "string" + }, + "statusReportingInterval": { + "default": "30s", + "description": "StatusReportingInterval is the interval for reporting status updates to Kubernetes.\nThis controls how often the vMCP runtime reports backend health and phase changes.\nLower values provide faster status updates but increase API server load.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "unhealthyThreshold": { + "default": 3, + "description": "UnhealthyThreshold is the number of consecutive failures before marking unhealthy.", + "type": "integer" + } + }, + "type": "object", + "additionalProperties": false + }, + "logLevel": { + "description": "LogLevel sets the logging level for the Virtual MCP server.\nThe only valid value is \"debug\" to enable debug logging.\nWhen omitted or empty, the server uses info level logging.", + "enum": [ + "debug" + ], + "type": "string" + }, + "timeouts": { + "description": "Timeouts configures timeout settings.", + "properties": { + "default": { + "default": "30s", + "description": "Default is the default timeout for backend requests.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "perWorkload": { + "additionalProperties": { + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "description": "PerWorkload defines per-workload timeout overrides.", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false + }, + "optimizer": { + "description": "Optimizer configures the MCP optimizer for context optimization on large toolsets.\nWhen enabled, vMCP exposes only find_tool and call_tool operations to clients\ninstead of all backend tools directly. This reduces token usage by allowing\nLLMs to discover relevant tools on demand rather than receiving all tool definitions.", + "properties": { + "embeddingService": { + "description": "EmbeddingService is the full base URL of the embedding service endpoint\n(e.g., http://my-embedding.default.svc.cluster.local:8080) for semantic\ntool discovery.\n\nIn a Kubernetes environment, it is more convenient to use the\nVirtualMCPServerSpec.EmbeddingServerRef field instead of setting this\ndirectly. EmbeddingServerRef references an EmbeddingServer CRD by name,\nand the operator automatically resolves the referenced resource's\nStatus.URL to populate this field. This provides managed lifecycle\n(the operator watches the EmbeddingServer for readiness and URL changes)\nand avoids hardcoding service URLs in the config. If both\nEmbeddingServerRef and this field are set, EmbeddingServerRef takes\nprecedence and this value is overridden with a warning.", + "type": "string" + }, + "embeddingServiceTimeout": { + "default": "30s", + "description": "EmbeddingServiceTimeout is the HTTP request timeout for calls to the embedding service.\nDefaults to 30s if not specified.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ns|us|\u00b5s|ms|s|m|h))+$", + "type": "string" + }, + "hybridSearchSemanticRatio": { + "description": "HybridSearchSemanticRatio controls the balance between semantic (meaning-based)\nand keyword search results. 0.0 = all keyword, 1.0 = all semantic.\nDefaults to \"0.5\" if not specified or empty.\nSerialized as a string because CRDs do not support float types portably.", + "pattern": "^([0-9]*[.])?[0-9]+$", + "type": "string" + }, + "maxToolsToReturn": { + "description": "MaxToolsToReturn is the maximum number of tool results returned by a search query.\nDefaults to 8 if not specified or zero.", + "maximum": 50, + "minimum": 1, + "type": "integer" + }, + "semanticDistanceThreshold": { + "description": "SemanticDistanceThreshold is the maximum distance for semantic search results.\nResults exceeding this threshold are filtered out from semantic search.\nThis threshold does not apply to keyword search.\nRange: 0 = identical, 2 = completely unrelated.\nDefaults to \"1.0\" if not specified or empty.\nSerialized as a string because CRDs do not support float types portably.", + "pattern": "^([0-9]*[.])?[0-9]+$", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "outgoingAuth": { + "description": "OutgoingAuth configures how the virtual MCP server authenticates to backends.\nWhen using the Kubernetes operator, this is populated by the converter from\nVirtualMCPServerSpec.OutgoingAuth and any values set here will be superseded.", + "properties": { + "backends": { + "additionalProperties": { + "description": "BackendAuthStrategy defines how to authenticate to a specific backend.\n\nThis struct provides type-safe configuration for different authentication strategies\nusing HeaderInjection or TokenExchange fields based on the Type field.", + "properties": { + "awsSts": { + "description": "AwsSts contains configuration for AWS STS auth strategy.\nUsed when Type = \"aws_sts\".", + "properties": { + "fallbackRoleArn": { + "description": "FallbackRoleArn is the IAM role ARN to assume when no role mappings match.", + "type": "string" + }, + "region": { + "description": "Region is the AWS region for the STS endpoint and service.", + "type": "string" + }, + "roleClaim": { + "description": "RoleClaim is the JWT claim to use for role mapping evaluation.", + "type": "string" + }, + "roleMappings": { + "description": "RoleMappings defines claim-based role selection rules.", + "items": { + "description": "RoleMapping defines a rule for mapping JWT claims to IAM roles.\nMappings are evaluated in priority order (lower number = higher priority).", + "properties": { + "claim": { + "description": "Claim is a simple claim value to match against the RoleClaim field.", + "type": "string" + }, + "matcher": { + "description": "Matcher is a CEL expression for complex matching against JWT claims.", + "type": "string" + }, + "priority": { + "description": "Priority determines evaluation order (lower values = higher priority).\nMirrors awssts.RoleMapping.Priority, which is *int because the role mapper\nuses math.MaxInt for nil-priority semantics in effectivePriority.", + "type": "integer" + }, + "roleArn": { + "description": "RoleArn is the IAM role ARN to assume when this mapping matches.", + "type": "string" + } + }, + "required": [ + "roleArn" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "service": { + "description": "Service is the AWS service name for SigV4 signing.", + "type": "string" + }, + "sessionDuration": { + "description": "SessionDuration is the duration in seconds for the STS session.", + "format": "int32", + "type": "integer" + }, + "sessionNameClaim": { + "description": "SessionNameClaim is the JWT claim to use for the role session name.", + "type": "string" + }, + "subjectProviderName": { + "description": "SubjectProviderName selects which upstream provider's token to use as the\nweb identity token for AssumeRoleWithWebIdentity. When set, the token is\nlooked up from Identity.UpstreamTokens instead of the request's\nAuthorization header.", + "type": "string" + } + }, + "required": [ + "region" + ], + "type": "object", + "additionalProperties": false + }, + "headerInjection": { + "description": "HeaderInjection contains configuration for header injection auth strategy.\nUsed when Type = \"header_injection\".", + "properties": { + "headerName": { + "description": "HeaderName is the name of the header to inject (e.g., \"Authorization\").", + "type": "string" + }, + "headerValue": { + "description": "HeaderValue is the static header value to inject.\nEither HeaderValue or HeaderValueEnv should be set, not both.", + "type": "string" + }, + "headerValueEnv": { + "description": "HeaderValueEnv is the environment variable name containing the header value.\nThe value will be resolved at runtime from this environment variable.\nEither HeaderValue or HeaderValueEnv should be set, not both.", + "type": "string" + } + }, + "required": [ + "headerName" + ], + "type": "object", + "additionalProperties": false + }, + "tokenExchange": { + "description": "TokenExchange contains configuration for token exchange auth strategy.\nUsed when Type = \"token_exchange\".", + "properties": { + "audience": { + "description": "Audience is the target audience for the exchanged token.", + "type": "string" + }, + "clientId": { + "description": "ClientID is the OAuth client ID for the token exchange request.", + "type": "string" + }, + "clientSecret": { + "description": "ClientSecret is the OAuth client secret (use ClientSecretEnv for security).", + "type": "string" + }, + "clientSecretEnv": { + "description": "ClientSecretEnv is the environment variable name containing the client secret.\nThe value will be resolved at runtime from this environment variable.", + "type": "string" + }, + "scopes": { + "description": "Scopes are the requested scopes for the exchanged token.", + "items": { + "type": "string" + }, + "type": "array" + }, + "subjectProviderName": { + "description": "SubjectProviderName selects which upstream provider's token to use as the\nsubject token. When set, the token is looked up from Identity.UpstreamTokens\ninstead of using Identity.Token.\nWhen left empty and an embedded authorization server is configured, the system\nautomatically populates this field with the first configured upstream provider name.\nSet it explicitly to override that default or to select a specific provider when\nmultiple upstreams are configured.", + "type": "string" + }, + "subjectTokenType": { + "description": "SubjectTokenType is the token type of the incoming subject token.\nDefaults to \"urn:ietf:params:oauth:token-type:access_token\" if not specified.", + "type": "string" + }, + "tokenUrl": { + "description": "TokenURL is the OAuth token endpoint URL for token exchange.", + "type": "string" + } + }, + "required": [ + "tokenUrl" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type is the auth strategy: \"unauthenticated\", \"header_injection\", \"token_exchange\", \"upstream_inject\", \"aws_sts\", \"obo\"", + "type": "string" + }, + "upstreamInject": { + "description": "UpstreamInject contains configuration for upstream inject auth strategy.\nUsed when Type = \"upstream_inject\".", + "properties": { + "providerName": { + "description": "ProviderName is the name of the upstream provider configured in the\nembedded authorization server. Must match an entry in AuthServer.Upstreams.", + "type": "string" + } + }, + "required": [ + "providerName" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "type" + ], + "type": "object", + "additionalProperties": false + }, + "description": "Backends contains per-backend auth configuration.", + "type": "object" + }, + "default": { + "description": "Default is the default auth strategy for backends without explicit config.", + "properties": { + "awsSts": { + "description": "AwsSts contains configuration for AWS STS auth strategy.\nUsed when Type = \"aws_sts\".", + "properties": { + "fallbackRoleArn": { + "description": "FallbackRoleArn is the IAM role ARN to assume when no role mappings match.", + "type": "string" + }, + "region": { + "description": "Region is the AWS region for the STS endpoint and service.", + "type": "string" + }, + "roleClaim": { + "description": "RoleClaim is the JWT claim to use for role mapping evaluation.", + "type": "string" + }, + "roleMappings": { + "description": "RoleMappings defines claim-based role selection rules.", + "items": { + "description": "RoleMapping defines a rule for mapping JWT claims to IAM roles.\nMappings are evaluated in priority order (lower number = higher priority).", + "properties": { + "claim": { + "description": "Claim is a simple claim value to match against the RoleClaim field.", + "type": "string" + }, + "matcher": { + "description": "Matcher is a CEL expression for complex matching against JWT claims.", + "type": "string" + }, + "priority": { + "description": "Priority determines evaluation order (lower values = higher priority).\nMirrors awssts.RoleMapping.Priority, which is *int because the role mapper\nuses math.MaxInt for nil-priority semantics in effectivePriority.", + "type": "integer" + }, + "roleArn": { + "description": "RoleArn is the IAM role ARN to assume when this mapping matches.", + "type": "string" + } + }, + "required": [ + "roleArn" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "service": { + "description": "Service is the AWS service name for SigV4 signing.", + "type": "string" + }, + "sessionDuration": { + "description": "SessionDuration is the duration in seconds for the STS session.", + "format": "int32", + "type": "integer" + }, + "sessionNameClaim": { + "description": "SessionNameClaim is the JWT claim to use for the role session name.", + "type": "string" + }, + "subjectProviderName": { + "description": "SubjectProviderName selects which upstream provider's token to use as the\nweb identity token for AssumeRoleWithWebIdentity. When set, the token is\nlooked up from Identity.UpstreamTokens instead of the request's\nAuthorization header.", + "type": "string" + } + }, + "required": [ + "region" + ], + "type": "object", + "additionalProperties": false + }, + "headerInjection": { + "description": "HeaderInjection contains configuration for header injection auth strategy.\nUsed when Type = \"header_injection\".", + "properties": { + "headerName": { + "description": "HeaderName is the name of the header to inject (e.g., \"Authorization\").", + "type": "string" + }, + "headerValue": { + "description": "HeaderValue is the static header value to inject.\nEither HeaderValue or HeaderValueEnv should be set, not both.", + "type": "string" + }, + "headerValueEnv": { + "description": "HeaderValueEnv is the environment variable name containing the header value.\nThe value will be resolved at runtime from this environment variable.\nEither HeaderValue or HeaderValueEnv should be set, not both.", + "type": "string" + } + }, + "required": [ + "headerName" + ], + "type": "object", + "additionalProperties": false + }, + "tokenExchange": { + "description": "TokenExchange contains configuration for token exchange auth strategy.\nUsed when Type = \"token_exchange\".", + "properties": { + "audience": { + "description": "Audience is the target audience for the exchanged token.", + "type": "string" + }, + "clientId": { + "description": "ClientID is the OAuth client ID for the token exchange request.", + "type": "string" + }, + "clientSecret": { + "description": "ClientSecret is the OAuth client secret (use ClientSecretEnv for security).", + "type": "string" + }, + "clientSecretEnv": { + "description": "ClientSecretEnv is the environment variable name containing the client secret.\nThe value will be resolved at runtime from this environment variable.", + "type": "string" + }, + "scopes": { + "description": "Scopes are the requested scopes for the exchanged token.", + "items": { + "type": "string" + }, + "type": "array" + }, + "subjectProviderName": { + "description": "SubjectProviderName selects which upstream provider's token to use as the\nsubject token. When set, the token is looked up from Identity.UpstreamTokens\ninstead of using Identity.Token.\nWhen left empty and an embedded authorization server is configured, the system\nautomatically populates this field with the first configured upstream provider name.\nSet it explicitly to override that default or to select a specific provider when\nmultiple upstreams are configured.", + "type": "string" + }, + "subjectTokenType": { + "description": "SubjectTokenType is the token type of the incoming subject token.\nDefaults to \"urn:ietf:params:oauth:token-type:access_token\" if not specified.", + "type": "string" + }, + "tokenUrl": { + "description": "TokenURL is the OAuth token endpoint URL for token exchange.", + "type": "string" + } + }, + "required": [ + "tokenUrl" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type is the auth strategy: \"unauthenticated\", \"header_injection\", \"token_exchange\", \"upstream_inject\", \"aws_sts\", \"obo\"", + "type": "string" + }, + "upstreamInject": { + "description": "UpstreamInject contains configuration for upstream inject auth strategy.\nUsed when Type = \"upstream_inject\".", + "properties": { + "providerName": { + "description": "ProviderName is the name of the upstream provider configured in the\nembedded authorization server. Must match an entry in AuthServer.Upstreams.", + "type": "string" + } + }, + "required": [ + "providerName" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "type" + ], + "type": "object", + "additionalProperties": false + }, + "source": { + "description": "Source defines how to discover backend auth: \"inline\", \"discovered\"\n- inline: Explicit configuration in OutgoingAuth\n- discovered: Auto-discover from backend MCPServer.externalAuthConfigRef (Kubernetes only)", + "type": "string" + } + }, + "required": [ + "source" + ], + "type": "object", + "additionalProperties": false + }, + "rateLimiting": { + "description": "RateLimiting defines rate limiting configuration for the Virtual MCP server.\nRequires Redis session storage to be configured for distributed rate limiting.", + "properties": { + "perUser": { + "description": "PerUser is a token bucket applied independently to each authenticated user\nat the server level. Requires authentication to be enabled.\nEach unique userID creates Redis keys that expire after 2x refillPeriod.\nMemory formula: unique_users_per_TTL_window * (1 + num_tools_with_per_user_limits) keys.", + "properties": { + "maxTokens": { + "description": "MaxTokens is the maximum number of tokens (bucket capacity).\nThis is also the burst size: the maximum number of requests that can be served\ninstantaneously before the bucket is depleted.", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "refillPeriod": { + "description": "RefillPeriod is the duration to fully refill the bucket from zero to maxTokens.\nThe effective refill rate is maxTokens / refillPeriod tokens per second.\nFormat: Go duration string (e.g., \"1m0s\", \"30s\", \"1h0m0s\").", + "type": "string" + } + }, + "required": [ + "maxTokens", + "refillPeriod" + ], + "type": "object", + "additionalProperties": false + }, + "shared": { + "description": "Shared is a token bucket shared across all users for the entire server.", + "properties": { + "maxTokens": { + "description": "MaxTokens is the maximum number of tokens (bucket capacity).\nThis is also the burst size: the maximum number of requests that can be served\ninstantaneously before the bucket is depleted.", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "refillPeriod": { + "description": "RefillPeriod is the duration to fully refill the bucket from zero to maxTokens.\nThe effective refill rate is maxTokens / refillPeriod tokens per second.\nFormat: Go duration string (e.g., \"1m0s\", \"30s\", \"1h0m0s\").", + "type": "string" + } + }, + "required": [ + "maxTokens", + "refillPeriod" + ], + "type": "object", + "additionalProperties": false + }, + "tools": { + "description": "Tools defines per-tool rate limit overrides.\nEach entry applies additional rate limits to calls targeting a specific tool name.\nA request must pass both the server-level limit and the per-tool limit.", + "items": { + "description": "ToolRateLimitConfig defines rate limits for a specific tool.\nAt least one of shared or perUser must be configured.", + "properties": { + "name": { + "description": "Name is the MCP tool name this limit applies to.", + "minLength": 1, + "type": "string" + }, + "perUser": { + "description": "PerUser token bucket configuration for this tool.", + "properties": { + "maxTokens": { + "description": "MaxTokens is the maximum number of tokens (bucket capacity).\nThis is also the burst size: the maximum number of requests that can be served\ninstantaneously before the bucket is depleted.", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "refillPeriod": { + "description": "RefillPeriod is the duration to fully refill the bucket from zero to maxTokens.\nThe effective refill rate is maxTokens / refillPeriod tokens per second.\nFormat: Go duration string (e.g., \"1m0s\", \"30s\", \"1h0m0s\").", + "type": "string" + } + }, + "required": [ + "maxTokens", + "refillPeriod" + ], + "type": "object", + "additionalProperties": false + }, + "shared": { + "description": "Shared token bucket for this specific tool.", + "properties": { + "maxTokens": { + "description": "MaxTokens is the maximum number of tokens (bucket capacity).\nThis is also the burst size: the maximum number of requests that can be served\ninstantaneously before the bucket is depleted.", + "format": "int32", + "minimum": 1, + "type": "integer" + }, + "refillPeriod": { + "description": "RefillPeriod is the duration to fully refill the bucket from zero to maxTokens.\nThe effective refill rate is maxTokens / refillPeriod tokens per second.\nFormat: Go duration string (e.g., \"1m0s\", \"30s\", \"1h0m0s\").", + "type": "string" + } + }, + "required": [ + "maxTokens", + "refillPeriod" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "name" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "at least one of shared or perUser must be configured", + "rule": "has(self.shared) || has(self.perUser)" + } + ], + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + } + }, + "type": "object", + "x-kubernetes-validations": [ + { + "message": "at least one of shared, perUser, or tools must be configured", + "rule": "has(self.shared) || has(self.perUser) || (has(self.tools) && size(self.tools) > 0)" + } + ], + "additionalProperties": false + }, + "sessionStorage": { + "description": "SessionStorage configures session storage for stateful horizontal scaling.\nWhen provider is \"redis\", the operator injects Redis connection parameters\n(address, db, keyPrefix) here. The Redis password is provided separately via\nthe THV_SESSION_REDIS_PASSWORD environment variable.", + "properties": { + "address": { + "description": "Address is the Redis server address (required when provider is redis).", + "type": "string" + }, + "db": { + "default": 0, + "description": "DB is the Redis database number.", + "format": "int32", + "minimum": 0, + "type": "integer" + }, + "keyPrefix": { + "description": "KeyPrefix is an optional prefix for all Redis keys used by ToolHive.", + "type": "string" + }, + "provider": { + "description": "Provider is the session storage backend type.", + "enum": [ + "memory", + "redis" + ], + "type": "string" + } + }, + "required": [ + "provider" + ], + "type": "object", + "additionalProperties": false + }, + "telemetry": { + "description": "Telemetry configures OpenTelemetry-based observability for the Virtual MCP server\nincluding distributed tracing, OTLP metrics export, and Prometheus metrics endpoint.\nDeprecated (Kubernetes operator only): When deploying via the operator, use\nVirtualMCPServer.spec.telemetryConfigRef to reference a shared MCPTelemetryConfig\nresource instead. This field remains valid for standalone (non-operator) deployments.", + "properties": { + "caCertPath": { + "description": "CACertPath is the file path to a CA certificate bundle for the OTLP endpoint.\nWhen set, the OTLP exporters use this CA to verify the collector's TLS certificate\ninstead of relying solely on the system CA pool.", + "type": "string" + }, + "customAttributes": { + "additionalProperties": { + "type": "string" + }, + "description": "CustomAttributes contains custom resource attributes to be added to all telemetry signals.\nThese are parsed from CLI flags (--otel-custom-attributes) or environment variables\n(OTEL_RESOURCE_ATTRIBUTES) as key=value pairs.", + "type": "object" + }, + "enablePrometheusMetricsPath": { + "default": false, + "description": "EnablePrometheusMetricsPath controls whether to expose Prometheus-style /metrics endpoint.\nThe metrics are served on the main transport port at /metrics.\nThis is separate from OTLP metrics which are sent to the Endpoint.", + "type": "boolean" + }, + "endpoint": { + "description": "Endpoint is the OTLP endpoint URL", + "type": "string" + }, + "environmentVariables": { + "description": "EnvironmentVariables is a list of environment variable names that should be\nincluded in telemetry spans as attributes. Only variables in this list will\nbe read from the host machine and included in spans for observability.\nExample: [\"NODE_ENV\", \"DEPLOYMENT_ENV\", \"SERVICE_VERSION\"]", + "items": { + "type": "string" + }, + "type": "array" + }, + "headers": { + "additionalProperties": { + "type": "string" + }, + "description": "Headers contains authentication headers for the OTLP endpoint.", + "type": "object" + }, + "insecure": { + "default": false, + "description": "Insecure indicates whether to use HTTP instead of HTTPS for the OTLP endpoint.", + "type": "boolean" + }, + "metricsEnabled": { + "default": false, + "description": "MetricsEnabled controls whether OTLP metrics are enabled.\nWhen false, OTLP metrics are not sent even if an endpoint is configured.\nThis is independent of EnablePrometheusMetricsPath.", + "type": "boolean" + }, + "samplingRate": { + "default": "0.05", + "description": "SamplingRate is the trace sampling rate (0.0-1.0) as a string.\nOnly used when TracingEnabled is true.\nExample: \"0.05\" for 5% sampling.", + "type": "string" + }, + "serviceName": { + "description": "ServiceName is the service name for telemetry.\nWhen omitted, defaults to the server name (e.g., VirtualMCPServer name).", + "type": "string" + }, + "serviceVersion": { + "description": "ServiceVersion is the service version for telemetry.\nWhen omitted, defaults to the ToolHive version.", + "type": "string" + }, + "tracingEnabled": { + "default": false, + "description": "TracingEnabled controls whether distributed tracing is enabled.\nWhen false, no tracer provider is created even if an endpoint is configured.", + "type": "boolean" + }, + "useLegacyAttributes": { + "default": true, + "description": "UseLegacyAttributes controls whether legacy (pre-MCP OTEL semconv) attribute names\nare emitted alongside the new standard attribute names. When true, spans include both\nold and new attribute names for backward compatibility with existing dashboards.\nCurrently defaults to true; this will change to false in a future release.", + "type": "boolean" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "x-kubernetes-preserve-unknown-fields": true, + "additionalProperties": false + }, + "embeddingServerRef": { + "description": "EmbeddingServerRef references an existing EmbeddingServer resource by name.\nWhen the optimizer is enabled, this field is required to point to a ready EmbeddingServer\nthat provides embedding capabilities.\nThe referenced EmbeddingServer must exist in the same namespace and be ready.", + "properties": { + "name": { + "description": "Name is the name of the EmbeddingServer resource", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "groupRef": { + "description": "GroupRef references the MCPGroup that defines backend workloads.\nThe referenced MCPGroup must exist in the same namespace.", + "properties": { + "name": { + "description": "Name is the name of the MCPGroup resource in the same namespace", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "imagePullSecrets": { + "description": "ImagePullSecrets allows specifying image pull secrets for the vMCP workload.\nThese are applied to both the vMCP Deployment's PodSpec.ImagePullSecrets\nand to the operator-managed ServiceAccount the vMCP server runs as, so private\nimages are pullable through either path.\n\nMerge semantics with PodTemplateSpec:\nThe deployed PodSpec.ImagePullSecrets is the Kubernetes-native strategic-merge\nunion of this field and spec.podTemplateSpec.spec.imagePullSecrets, merged by\nthe patchStrategy:\"merge\" / patchMergeKey:\"name\" tags on corev1.PodSpec.\n - This field is rendered first as the controller-generated default.\n - spec.podTemplateSpec.spec.imagePullSecrets is then strategic-merge-patched\n on top, keyed by Name. Distinct names from the two sources are unioned in\n the resulting list; entries with the same Name are deduplicated and the\n PodTemplateSpec entry wins on overlap (user override).\n - Order in the resulting list is not guaranteed and should not be relied on:\n strategic merge by name is order-insensitive.\n - The operator-managed ServiceAccount's imagePullSecrets list is populated\n ONLY from this field. spec.podTemplateSpec.spec.imagePullSecrets does not\n reach the ServiceAccount because PodTemplateSpec has no notion of a\n ServiceAccount. To make a secret usable via the ServiceAccount path\n (e.g. for sidecars or init containers that pull images independently),\n list it here rather than under spec.podTemplateSpec.\n\nNote on cross-CRD consistency:\nMCPRegistry currently uses an atomic-replace strategy for its imagePullSecrets\n(the user-provided value replaces the controller-generated list rather than\nbeing merged on top). VirtualMCPServer follows the Kubernetes-native\nstrategic-merge-by-name behavior described above. Aligning the two is tracked\nas a separate follow-up; until then, manifests that set imagePullSecrets on\nboth CRDs will see different override behavior between them.", + "items": { + "description": "LocalObjectReference contains enough information to let you locate the\nreferenced object inside the same namespace.", + "properties": { + "name": { + "default": "", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + } + }, + "type": "object", + "x-kubernetes-map-type": "atomic", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "incomingAuth": { + "description": "IncomingAuth configures authentication for clients connecting to the Virtual MCP server.\nMust be explicitly set - use \"anonymous\" type when no authentication is required.\nThis field takes precedence over config.IncomingAuth and should be preferred because it\nsupports Kubernetes-native secret references (SecretKeyRef, ConfigMapRef) for secure\ndynamic discovery of credentials, rather than requiring secrets to be embedded in config.", + "properties": { + "authzConfig": { + "description": "AuthzConfig defines authorization policy configuration\nReuses MCPServer authz patterns", + "properties": { + "configMap": { + "description": "ConfigMap references a ConfigMap containing authorization configuration\nOnly used when Type is \"configMap\"", + "properties": { + "key": { + "default": "authz.json", + "description": "Key is the key in the ConfigMap that contains the authorization configuration", + "type": "string" + }, + "name": { + "description": "Name is the name of the ConfigMap", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "groupClaimName": { + "description": "GroupClaimName is the JWT claim key that contains group membership for the\nprincipal. When set, takes priority over the well-known defaults\n(\"groups\", \"roles\", \"cognito:groups\"). Use this for IDPs that place\ngroups under a URI-style claim (e.g. \"https://example.com/groups\"). When\nType is \"configMap\", a group_claim_name entry in the referenced ConfigMap\nis overridden by this field if both are set.", + "maxLength": 253, + "type": "string" + }, + "groupEntityType": { + "description": "GroupEntityType is the Cedar entity type name used for principal parent\nUIDs synthesised from JWT group/role claims. Defaults to \"THVGroup\" when\nempty. Must match the entity type used in the static entity store for\ntransitive `in` checks (e.g. `ClaimGroup \u2192 PlatformRole`) to resolve.\nNamespaced names (`Foo::Bar`) are not yet supported. When Type is\n\"configMap\", a group_entity_type entry in the referenced ConfigMap is\noverridden by this field if both are set.", + "maxLength": 63, + "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", + "type": "string" + }, + "inline": { + "description": "Inline contains direct authorization configuration\nOnly used when Type is \"inline\"", + "properties": { + "entitiesJson": { + "default": "[]", + "description": "EntitiesJSON is a JSON string representing Cedar entities. Required when\ntransitive policies (e.g. `ClaimGroup \u2192 PlatformRole`) need a static\nentity store; defaults to \"[]\".", + "type": "string" + }, + "policies": { + "description": "Policies is a list of Cedar policy strings", + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "primaryUpstreamProvider": { + "description": "PrimaryUpstreamProvider names the upstream IDP whose access token's\nclaims Cedar should evaluate.\n\nDeprecated: on VirtualMCPServer this field has moved to\nspec.authServerConfig.primaryUpstreamProvider. The old location is\nstill read for one release for backward compatibility; the\nVirtualMCPServer controller emits an AuthzPrimaryUpstreamProviderDeprecated\nWarning event whenever it is consumed, and removal is planned for the\nrelease after the deprecation cycle.\n\nOn MCPServer and MCPRemoteProxy this field has always been a structural\nno-op (those CRDs do not run an embedded auth server). Setting it\ncontinues to surface the AuthzPrimaryUpstreamProviderIgnored advisory\ncondition; the deprecation does not change that behaviour.", + "maxLength": 63, + "minLength": 1, + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "type": "string" + } + }, + "required": [ + "policies" + ], + "type": "object", + "additionalProperties": false + }, + "roleClaimName": { + "description": "RoleClaimName is the JWT claim key that contains role membership for the\nprincipal. When set, the claim is extracted separately from GroupClaimName\nand both are mapped to the configured GroupEntityType. When Type is\n\"configMap\", a role_claim_name entry in the referenced ConfigMap is\noverridden by this field if both are set.", + "maxLength": 253, + "type": "string" + }, + "type": { + "default": "configMap", + "description": "Type is the type of authorization configuration", + "enum": [ + "configMap", + "inline" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "configMap must be set when type is 'configMap', and must not be set otherwise", + "rule": "self.type == 'configMap' ? has(self.configMap) : !has(self.configMap)" + }, + { + "message": "inline must be set when type is 'inline', and must not be set otherwise", + "rule": "self.type == 'inline' ? has(self.inline) : !has(self.inline)" + } + ], + "additionalProperties": false + }, + "oidcConfigRef": { + "description": "OIDCConfigRef references a shared MCPOIDCConfig resource for OIDC authentication.\nThe referenced MCPOIDCConfig must exist in the same namespace as this VirtualMCPServer.\nPer-server overrides (audience, scopes) are specified here; shared provider config\nlives in the MCPOIDCConfig resource.", + "properties": { + "audience": { + "description": "Audience is the expected audience for token validation.\nThis MUST be unique per server to prevent token replay attacks.", + "minLength": 1, + "type": "string" + }, + "name": { + "description": "Name is the name of the MCPOIDCConfig resource", + "minLength": 1, + "type": "string" + }, + "resourceUrl": { + "description": "ResourceURL is the public URL for OAuth protected resource metadata (RFC 9728).\nWhen the server is exposed via Ingress or gateway, set this to the external\nURL that MCP clients connect to. If not specified, defaults to the internal\nKubernetes service URL.", + "type": "string" + }, + "scopes": { + "description": "Scopes is the list of OAuth scopes to advertise in the well-known endpoint (RFC 9728).\nIf empty, defaults to [\"openid\"].", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "required": [ + "audience", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type defines the authentication type: anonymous or oidc\nWhen no authentication is required, explicitly set this to \"anonymous\"", + "enum": [ + "anonymous", + "oidc" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "spec.incomingAuth.oidcConfigRef is required when type is oidc", + "rule": "self.type == 'oidc' ? has(self.oidcConfigRef) : true" + } + ], + "additionalProperties": false + }, + "outgoingAuth": { + "description": "OutgoingAuth configures authentication from Virtual MCP to backend MCPServers.\nThis field takes precedence over config.OutgoingAuth and should be preferred because it\nsupports Kubernetes-native secret references (SecretKeyRef, ConfigMapRef) for secure\ndynamic discovery of credentials, rather than requiring secrets to be embedded in config.", + "properties": { + "backends": { + "additionalProperties": { + "description": "BackendAuthConfig defines authentication configuration for a backend MCPServer", + "properties": { + "externalAuthConfigRef": { + "description": "ExternalAuthConfigRef references an MCPExternalAuthConfig resource\nOnly used when Type is \"externalAuthConfigRef\"", + "properties": { + "name": { + "description": "Name is the name of the MCPExternalAuthConfig resource", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type defines the authentication type", + "enum": [ + "discovered", + "externalAuthConfigRef" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "additionalProperties": false + }, + "description": "Backends defines per-backend authentication overrides\nWorks in all modes (discovered, inline)", + "type": "object" + }, + "default": { + "description": "Default defines default behavior for backends without explicit auth config", + "properties": { + "externalAuthConfigRef": { + "description": "ExternalAuthConfigRef references an MCPExternalAuthConfig resource\nOnly used when Type is \"externalAuthConfigRef\"", + "properties": { + "name": { + "description": "Name is the name of the MCPExternalAuthConfig resource", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": { + "description": "Type defines the authentication type", + "enum": [ + "discovered", + "externalAuthConfigRef" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "additionalProperties": false + }, + "source": { + "default": "discovered", + "description": "Source defines how backend authentication configurations are determined\n- discovered: Automatically discover from backend's MCPServer.spec.externalAuthConfigRef\n- inline: Explicit per-backend configuration in VirtualMCPServer", + "enum": [ + "discovered", + "inline" + ], + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "podTemplateSpec": { + "description": "PodTemplateSpec defines the pod template to use for the Virtual MCP server\nThis allows for customizing the pod configuration beyond what is provided by the other fields.\nNote that to modify the specific container the Virtual MCP server runs in, you must specify\nthe 'vmcp' container name in the PodTemplateSpec.\nThis field accepts a PodTemplateSpec object as JSON/YAML.", + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "replicas": { + "description": "Replicas is the desired number of vMCP pod replicas.\nVirtualMCPServer creates a single Deployment for the vMCP aggregator process,\nso there is only one replicas field (unlike MCPServer which has separate\nReplicas and BackendReplicas for its two Deployments).\nWhen nil, the operator does not set Deployment.Spec.Replicas, leaving replica\nmanagement to an HPA or other external controller.", + "format": "int32", + "minimum": 0, + "type": "integer" + }, + "serviceAccount": { + "description": "ServiceAccount is the name of an already existing service account to use by the Virtual MCP server.\nIf not specified, a ServiceAccount will be created automatically and used by the Virtual MCP server.", + "type": "string" + }, + "serviceType": { + "default": "ClusterIP", + "description": "ServiceType specifies the Kubernetes service type for the Virtual MCP server", + "enum": [ + "ClusterIP", + "NodePort", + "LoadBalancer" + ], + "type": "string" + }, + "sessionAffinity": { + "default": "ClientIP", + "description": "SessionAffinity controls whether the Service routes repeated client connections to the same pod.\nMCP protocols (SSE, streamable-http) are stateful, so ClientIP is the default.\nSet to \"None\" for stateless servers or when using an external load balancer with its own affinity.", + "enum": [ + "ClientIP", + "None" + ], + "type": "string" + }, + "sessionStorage": { + "description": "SessionStorage configures session storage for stateful horizontal scaling.\nWhen nil, no session storage is configured.", + "properties": { + "address": { + "description": "Address is the Redis server address (required when provider is redis)", + "minLength": 1, + "type": "string" + }, + "db": { + "default": 0, + "description": "DB is the Redis database number", + "format": "int32", + "minimum": 0, + "type": "integer" + }, + "keyPrefix": { + "description": "KeyPrefix is an optional prefix for all Redis keys used by ToolHive", + "type": "string" + }, + "passwordRef": { + "description": "PasswordRef is a reference to a Secret key containing the Redis password", + "properties": { + "key": { + "description": "Key is the key within the secret", + "type": "string" + }, + "name": { + "description": "Name is the name of the secret", + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "provider": { + "description": "Provider is the session storage backend type", + "enum": [ + "memory", + "redis" + ], + "type": "string" + } + }, + "required": [ + "provider" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "address is required", + "rule": "self.provider == 'redis' ? has(self.address) : true" + } + ], + "additionalProperties": false + }, + "telemetryConfigRef": { + "description": "TelemetryConfigRef references an MCPTelemetryConfig resource for shared telemetry configuration.\nThe referenced MCPTelemetryConfig must exist in the same namespace as this VirtualMCPServer.\nCross-namespace references are not supported for security and isolation reasons.", + "properties": { + "name": { + "description": "Name is the name of the MCPTelemetryConfig resource", + "minLength": 1, + "type": "string" + }, + "serviceName": { + "description": "ServiceName overrides the telemetry service name for this specific server.\nThis MUST be unique per server for proper observability (e.g., distinguishing\ntraces and metrics from different servers sharing the same collector).\nIf empty, defaults to the server name with \"thv-\" prefix at runtime.", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "groupRef", + "incomingAuth" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "config.rateLimiting requires sessionStorage with provider 'redis'", + "rule": "!has(self.config) || !has(self.config.rateLimiting) || (has(self.sessionStorage) && self.sessionStorage.provider == 'redis')" + }, + { + "message": "config.rateLimiting.perUser requires incomingAuth.type oidc", + "rule": "!(has(self.config) && has(self.config.rateLimiting) && has(self.config.rateLimiting.perUser)) || (has(self.incomingAuth) && self.incomingAuth.type == 'oidc')" + }, + { + "message": "per-tool perUser rate limiting requires incomingAuth.type oidc", + "rule": "!has(self.config) || !has(self.config.rateLimiting) || !has(self.config.rateLimiting.tools) || self.config.rateLimiting.tools.all(t, !has(t.perUser)) || (has(self.incomingAuth) && self.incomingAuth.type == 'oidc')" + } + ], + "additionalProperties": false + }, + "status": { + "description": "VirtualMCPServerStatus defines the observed state of VirtualMCPServer", + "properties": { + "backendCount": { + "description": "BackendCount is the number of routable backends (ready + unauthenticated).\nExcludes unavailable, degraded, and unknown backends.", + "format": "int32", + "type": "integer" + }, + "conditions": { + "description": "Conditions represent the latest available observations of the VirtualMCPServer's state", + "items": { + "description": "Condition contains details for one aspect of the current state of this API Resource.", + "properties": { + "lastTransitionTime": { + "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", + "maxLength": 32768, + "type": "string" + }, + "observedGeneration": { + "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "reason": { + "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", + "maxLength": 1024, + "minLength": 1, + "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", + "type": "string" + }, + "status": { + "description": "status of the condition, one of True, False, Unknown.", + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", + "maxLength": 316, + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", + "type": "string" + } + }, + "required": [ + "lastTransitionTime", + "message", + "reason", + "status", + "type" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" + }, + "discoveredBackends": { + "description": "DiscoveredBackends lists discovered backend configurations from the MCPGroup", + "items": { + "description": "DiscoveredBackend represents a backend server discovered by vMCP runtime.\nThis type is shared with the Kubernetes operator CRD (VirtualMCPServer.Status.DiscoveredBackends).", + "properties": { + "authConfigRef": { + "description": "AuthConfigRef is the name of the discovered MCPExternalAuthConfig (if any)", + "type": "string" + }, + "authType": { + "description": "AuthType is the type of authentication configured", + "type": "string" + }, + "circuitBreakerState": { + "description": "CircuitBreakerState is the current circuit breaker state (closed, open, half-open).\nEmpty when circuit breaker is disabled or not configured.", + "enum": [ + "closed", + "open", + "half-open" + ], + "type": "string" + }, + "circuitLastChanged": { + "description": "CircuitLastChanged is the timestamp when the circuit breaker state last changed.\nEmpty when circuit breaker is disabled or has never changed state.", + "format": "date-time", + "type": "string" + }, + "consecutiveFailures": { + "description": "ConsecutiveFailures is the current count of consecutive health check failures.\nResets to 0 when the backend becomes healthy again.", + "type": "integer" + }, + "lastHealthCheck": { + "description": "LastHealthCheck is the timestamp of the last health check", + "format": "date-time", + "type": "string" + }, + "message": { + "description": "Message provides additional information about the backend status", + "type": "string" + }, + "name": { + "description": "Name is the name of the backend MCPServer", + "type": "string" + }, + "status": { + "description": "Status is the current status of the backend (ready, degraded, unavailable, unauthenticated, unknown).\nUse BackendHealthStatus.ToCRDStatus() to populate this field.", + "type": "string" + }, + "url": { + "description": "URL is the URL of the backend MCPServer", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "name" + ], + "x-kubernetes-list-type": "map" + }, + "message": { + "description": "Message provides additional information about the current phase", + "type": "string" + }, + "observedGeneration": { + "description": "ObservedGeneration is the most recent generation observed for this VirtualMCPServer", + "format": "int64", + "type": "integer" + }, + "oidcConfigHash": { + "description": "OIDCConfigHash is the hash of the referenced MCPOIDCConfig spec for change detection.\nOnly populated when IncomingAuth.OIDCConfigRef is set.", + "type": "string" + }, + "phase": { + "default": "Pending", + "description": "Phase is the current phase of the VirtualMCPServer", + "enum": [ + "Pending", + "Ready", + "Degraded", + "Failed" + ], + "type": "string" + }, + "telemetryConfigHash": { + "description": "TelemetryConfigHash is the hash of the referenced MCPTelemetryConfig spec for change detection.\nOnly populated when TelemetryConfigRef is set.", + "type": "string" + }, + "url": { + "description": "URL is the URL where the Virtual MCP server can be accessed", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object" +}