diff --git a/.github/workflows/operator-ci.yml b/.github/workflows/operator-ci.yml index 45548f0b8..bd4425543 100644 --- a/.github/workflows/operator-ci.yml +++ b/.github/workflows/operator-ci.yml @@ -95,7 +95,7 @@ jobs: - name: Check for changes id: git-check run: | - git diff --exit-code deploy/charts/operator-crds/crds || echo "crd-changes=true" >> $GITHUB_OUTPUT + git diff --exit-code deploy/charts/operator-crds/templates || echo "crd-changes=true" >> $GITHUB_OUTPUT git diff --exit-code deploy/charts/operator/templates || echo "operator-changes=true" >> $GITHUB_OUTPUT - name: Fail if CRDs are not up to date diff --git a/.gitignore b/.gitignore index 9f1c59641..7f5be4bf1 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,5 @@ cmd/thv-operator/.task/checksum/crdref-gen # Test coverage coverage* + +crd-helm-wrapper \ No newline at end of file diff --git a/cmd/thv-operator/Taskfile.yml b/cmd/thv-operator/Taskfile.yml index f0005c19d..1a4b4e532 100644 --- a/cmd/thv-operator/Taskfile.yml +++ b/cmd/thv-operator/Taskfile.yml @@ -182,15 +182,28 @@ tasks: operator-manifests: desc: Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects + vars: + PROJECT_ROOT: + sh: git rev-parse --show-toplevel || pwd + CONTROLLER_GEN_PATHS: + sh: | + if [[ "$PWD" == *"/cmd/thv-operator"* ]]; then + echo "./..." + else + echo "./cmd/thv-operator/..." + fi cmds: - - cmd: mkdir -p bin + - cmd: mkdir -p {{.PROJECT_ROOT}}/cmd/thv-operator/bin platforms: [linux, darwin] - - cmd: cmd.exe /c mkdir bin + - cmd: cmd.exe /c mkdir {{.PROJECT_ROOT}}/cmd/thv-operator/bin platforms: [windows] ignore_error: true # Windows has no mkdir -p, so just ignore error if it exists - go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.17.3 - - $(go env GOPATH)/bin/controller-gen crd webhook paths="./cmd/thv-operator/..." output:crd:artifacts:config=deploy/charts/operator-crds/crds - - $(go env GOPATH)/bin/controller-gen rbac:roleName=toolhive-operator-manager-role paths="./cmd/thv-operator/..." output:rbac:artifacts:config=deploy/charts/operator/templates/clusterrole + - $(go env GOPATH)/bin/controller-gen rbac:roleName=toolhive-operator-manager-role paths="{{.CONTROLLER_GEN_PATHS}}" output:rbac:artifacts:config={{.PROJECT_ROOT}}/deploy/charts/operator/templates/clusterrole + - $(go env GOPATH)/bin/controller-gen crd webhook paths="{{.CONTROLLER_GEN_PATHS}}" output:crd:artifacts:config={{.PROJECT_ROOT}}/deploy/charts/operator-crds/files/crds + # Wrap CRDs with Helm templates for conditional installation + - go run {{.PROJECT_ROOT}}/deploy/charts/operator-crds/crd-helm-wrapper/main.go -source {{.PROJECT_ROOT}}/deploy/charts/operator-crds/files/crds -target {{.PROJECT_ROOT}}/deploy/charts/operator-crds/templates + # - "{{.PROJECT_ROOT}}/deploy/charts/operator-crds/scripts/wrap-crds.sh" operator-test: desc: Run tests for the operator diff --git a/cmd/thv-operator/test-integration/mcp-external-auth/suite_test.go b/cmd/thv-operator/test-integration/mcp-external-auth/suite_test.go index 8a3d9f989..a81cc7df9 100644 --- a/cmd/thv-operator/test-integration/mcp-external-auth/suite_test.go +++ b/cmd/thv-operator/test-integration/mcp-external-auth/suite_test.go @@ -57,7 +57,7 @@ var _ = BeforeSuite(func() { By("bootstrapping test environment") testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "..", "deploy", "charts", "operator-crds", "crds")}, + CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "..", "deploy", "charts", "operator-crds", "files", "crds")}, ErrorIfCRDPathMissing: true, } diff --git a/cmd/thv-operator/test-integration/mcp-group/suite_test.go b/cmd/thv-operator/test-integration/mcp-group/suite_test.go index cbfb64b27..19d175ea5 100644 --- a/cmd/thv-operator/test-integration/mcp-group/suite_test.go +++ b/cmd/thv-operator/test-integration/mcp-group/suite_test.go @@ -58,7 +58,7 @@ var _ = BeforeSuite(func() { By("bootstrapping test environment") testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "..", "deploy", "charts", "operator-crds", "crds")}, + CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "..", "deploy", "charts", "operator-crds", "files", "crds")}, ErrorIfCRDPathMissing: true, } diff --git a/cmd/thv-operator/test-integration/mcp-registry/suite_test.go b/cmd/thv-operator/test-integration/mcp-registry/suite_test.go index 516284300..90fedba17 100644 --- a/cmd/thv-operator/test-integration/mcp-registry/suite_test.go +++ b/cmd/thv-operator/test-integration/mcp-registry/suite_test.go @@ -68,7 +68,7 @@ var _ = BeforeSuite(func() { testEnv = &envtest.Environment{ UseExistingCluster: &useExistingCluster, CRDDirectoryPaths: []string{ - filepath.Join("..", "..", "..", "..", "deploy", "charts", "operator-crds", "crds"), + filepath.Join("..", "..", "..", "..", "deploy", "charts", "operator-crds", "files", "crds"), }, ErrorIfCRDPathMissing: true, BinaryAssetsDirectory: kubebuilderAssets, diff --git a/cmd/thv-operator/test-integration/mcp-server/suite_test.go b/cmd/thv-operator/test-integration/mcp-server/suite_test.go index 17acaab3b..0ef7c99a7 100644 --- a/cmd/thv-operator/test-integration/mcp-server/suite_test.go +++ b/cmd/thv-operator/test-integration/mcp-server/suite_test.go @@ -61,7 +61,7 @@ var _ = BeforeSuite(func() { By("bootstrapping test environment") testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "..", "deploy", "charts", "operator-crds", "crds")}, + CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "..", "deploy", "charts", "operator-crds", "files", "crds")}, ErrorIfCRDPathMissing: true, } diff --git a/cmd/thv-operator/test-integration/virtualmcp/suite_test.go b/cmd/thv-operator/test-integration/virtualmcp/suite_test.go index 54f18616b..c520eb78f 100644 --- a/cmd/thv-operator/test-integration/virtualmcp/suite_test.go +++ b/cmd/thv-operator/test-integration/virtualmcp/suite_test.go @@ -61,7 +61,7 @@ var _ = BeforeSuite(func() { By("bootstrapping test environment") testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "..", "deploy", "charts", "operator-crds", "crds")}, + CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "..", "deploy", "charts", "operator-crds", "files", "crds")}, ErrorIfCRDPathMissing: true, } diff --git a/deploy/charts/operator-crds/.helmignore b/deploy/charts/operator-crds/.helmignore index 0e8a0eb36..f1a989f94 100644 --- a/deploy/charts/operator-crds/.helmignore +++ b/deploy/charts/operator-crds/.helmignore @@ -21,3 +21,9 @@ .idea/ *.tmproj .vscode/ +# Source CRD files and wrapper tool (only wrapped templates are needed) +files/ +crd-helm-wrapper/ +# Documentation +CLAUDE.md +CONTRIBUTING.md diff --git a/deploy/charts/operator-crds/Chart.yaml b/deploy/charts/operator-crds/Chart.yaml index 29034b8a3..ab04bc892 100644 --- a/deploy/charts/operator-crds/Chart.yaml +++ b/deploy/charts/operator-crds/Chart.yaml @@ -2,5 +2,5 @@ apiVersion: v2 name: toolhive-operator-crds description: A Helm chart for installing the ToolHive Operator CRDs into Kubernetes. type: application -version: 0.0.79 +version: 0.0.80 appVersion: "0.0.1" diff --git a/deploy/charts/operator-crds/README.md b/deploy/charts/operator-crds/README.md index e8aad3083..608634645 100644 --- a/deploy/charts/operator-crds/README.md +++ b/deploy/charts/operator-crds/README.md @@ -1,6 +1,6 @@ # ToolHive Operator CRDs Helm Chart -![Version: 0.0.79](https://img.shields.io/badge/Version-0.0.79-informational?style=flat-square) +![Version: 0.0.80](https://img.shields.io/badge/Version-0.0.80-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) A Helm chart for installing the ToolHive Operator CRDs into Kubernetes. @@ -40,3 +40,22 @@ To uninstall/delete the `toolhive-operator-crds` deployment: helm uninstall ``` +## Why CRDs in templates/? + +Helm does not upgrade CRDs placed in the `crds/` directory during `helm upgrade` operations. This is a [known Helm limitation](https://helm.sh/docs/chart_best_practices/custom_resource_definitions/#some-caveats-and-explanations) to prevent accidental data loss. As a result, users running `helm upgrade` would silently have stale CRDs. + +To ensure CRDs are upgraded alongside the chart, this chart places CRDs in `templates/` with Helm conditionals. This follows the pattern used by several popular projects. + +However, placing CRDs in `templates/` means they would be deleted when the Helm release is uninstalled, which could result in data loss. To prevent this, CRDs are annotated with `helm.sh/resource-policy: keep` by default (controlled by `crds.keep`). This ensures CRDs persist even after uninstalling the chart. + +## Values + +| Key | Type | Default | Description | +|-----|-------------|------|---------| +| crds | object | `{"install":{"registry":true,"server":true,"virtualMcp":true},"keep":true}` | CRD installation configuration | +| crds.install | object | `{"registry":true,"server":true,"virtualMcp":true}` | Feature flags for CRD groups | +| crds.install.registry | bool | `true` | Install Registry CRDs (mcpregistries) | +| crds.install.server | bool | `true` | Install Server CRDs (mcpservers, mcpremoteproxies, mcptoolconfigs, mcpgroups) | +| crds.install.virtualMcp | bool | `true` | Install VirtualMCP CRDs (virtualmcpservers, virtualmcpcompositetooldefinitions) | +| crds.keep | bool | `true` | Whether to add the "helm.sh/resource-policy: keep" annotation to CRDs When true, CRDs will not be deleted when the Helm release is uninstalled | + diff --git a/deploy/charts/operator-crds/README.md.gotmpl b/deploy/charts/operator-crds/README.md.gotmpl index d6d24d0b5..41374b3ed 100644 --- a/deploy/charts/operator-crds/README.md.gotmpl +++ b/deploy/charts/operator-crds/README.md.gotmpl @@ -48,6 +48,14 @@ To uninstall/delete the `toolhive-operator-crds` deployment: helm uninstall ``` +## Why CRDs in templates/? + +Helm does not upgrade CRDs placed in the `crds/` directory during `helm upgrade` operations. This is a [known Helm limitation](https://helm.sh/docs/chart_best_practices/custom_resource_definitions/#some-caveats-and-explanations) to prevent accidental data loss. As a result, users running `helm upgrade` would silently have stale CRDs. + +To ensure CRDs are upgraded alongside the chart, this chart places CRDs in `templates/` with Helm conditionals. This follows the pattern used by several popular projects. + +However, placing CRDs in `templates/` means they would be deleted when the Helm release is uninstalled, which could result in data loss. To prevent this, CRDs are annotated with `helm.sh/resource-policy: keep` by default (controlled by `crds.keep`). This ensures CRDs persist even after uninstalling the chart. + {{ template "chart.requirementsSection" . }} {{ template "chart.valuesSection" . }} diff --git a/deploy/charts/operator-crds/crd-helm-wrapper/README.md b/deploy/charts/operator-crds/crd-helm-wrapper/README.md new file mode 100644 index 000000000..e8e838ba8 --- /dev/null +++ b/deploy/charts/operator-crds/crd-helm-wrapper/README.md @@ -0,0 +1,123 @@ +# CRD Helm Wrapper + +A Go tool that wraps Kubernetes CRD YAML files with Helm template conditionals for: +- **Feature flags** (`crds.install.server`, `crds.install.registry`, `crds.install.virtualMcp`) +- **Resource policy annotations** (`crds.keep` โ†’ `helm.sh/resource-policy: keep`) + +## Why This Tool? + +Helm does not upgrade CRDs during `helm upgrade` operations. CRDs placed in the `crds/` directory are only processed during initial installation. By placing CRDs in `templates/` with conditionals, they are upgraded alongside the chart. + +Raw CRDs generated by `controller-gen` don't have Helm templating. This tool wraps them with conditionals so users can: +- Enable/disable specific CRD groups via feature flags +- Prevent Helm from deleting CRDs on uninstall + +## Usage + +```bash +# Run the tool +go run main.go \ + -source ../files/crds \ + -target ../templates \ + -verbose +``` + +## Flags + +| Flag | Description | Required | +|------|-------------|----------| +| `-source` | Source directory containing raw CRD YAML files | Yes | +| `-target` | Target directory for wrapped Helm templates | Yes | +| `-verbose` | Enable verbose output | No | + +## Feature Flag Groups + +CRDs are grouped by feature flags. Some CRDs belong to multiple groups: + +| Flag | CRDs | +|------|------| +| `crds.install.server` | mcpservers, mcpremoteproxies, mcptoolconfigs, mcpgroups | +| `crds.install.registry` | mcpregistries | +| `crds.install.virtualMcp` | virtualmcpservers, virtualmcpcompositetooldefinitions | +| `crds.install.server` OR `crds.install.virtualMcp` | mcpexternalauthconfigs (shared) | + +## Output Format + +For single-flag CRDs: + +```yaml +{{- if .Values.crds.install.server }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + {{- if .Values.crds.keep }} + helm.sh/resource-policy: keep + {{- end }} + controller-gen.kubebuilder.io/version: v0.17.3 + name: mcpservers.toolhive.stacklok.dev +spec: + ... +{{- end }} +``` + +For shared CRDs (multiple groups): + +```yaml +{{- if or .Values.crds.install.server .Values.crds.install.virtualMcp }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + {{- if .Values.crds.keep }} + helm.sh/resource-policy: keep + {{- end }} + controller-gen.kubebuilder.io/version: v0.17.3 + name: mcpexternalauthconfigs.toolhive.stacklok.dev +spec: + ... +{{- end }} +``` + +## Values Configuration + +The wrapped CRDs expect these values: + +```yaml +crds: + # Whether to add the "helm.sh/resource-policy: keep" annotation + keep: true + # Feature flags for CRD groups + install: + server: true + registry: true + virtualMcp: true +``` + +## Template Escaping + +CRD descriptions may contain Go template-like syntax (e.g., `{{.steps.step_id.output}}`). The tool automatically escapes these to prevent Helm from interpreting them as template directives. + +## Template Files + +The tool uses embedded template files in the `templates/` directory: + +| File | Purpose | +|------|---------| +| `header.tpl` | Opening conditional with `__FEATURE_CONDITION__` placeholder | +| `footer.tpl` | Closing `{{- end }}` | +| `keep-annotation.tpl` | Conditional `helm.sh/resource-policy: keep` annotation | + +## Adding New CRD Groups + +To add a new feature flag group, update the `crdFeatureFlags` map in `main.go`: + +```go +var crdFeatureFlags = map[string][]string{ + "newcrdtype": {"newFeature"}, + // For shared CRDs: + "sharedcrd": {"server", "newFeature"}, +} +``` + +Then add the corresponding value to `values.yaml`. diff --git a/deploy/charts/operator-crds/crd-helm-wrapper/main.go b/deploy/charts/operator-crds/crd-helm-wrapper/main.go new file mode 100644 index 000000000..0e9f49161 --- /dev/null +++ b/deploy/charts/operator-crds/crd-helm-wrapper/main.go @@ -0,0 +1,268 @@ +// Copyright 2025 Stacklok, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// crd-helm-wrapper wraps Kubernetes CRD YAML files with Helm template +// conditionals for feature-flagged installation and resource policy annotations. +package main + +import ( + "bufio" + "bytes" + "embed" + "flag" + "fmt" + "os" + "path/filepath" + "strings" + + "gopkg.in/yaml.v3" +) + +//go:embed templates/*.tpl +var templateFS embed.FS + +// crdFeatureFlags maps CRD plural names to their Helm values feature flags. +// CRDs can belong to multiple groups (e.g., mcpexternalauthconfigs is shared). +var crdFeatureFlags = map[string][]string{ + "mcpservers": {"server"}, + "mcpremoteproxies": {"server"}, + "mcptoolconfigs": {"server"}, + "mcpgroups": {"server"}, + "mcpregistries": {"registry"}, + "virtualmcpservers": {"virtualMcp"}, + "virtualmcpcompositetooldefinitions": {"virtualMcp"}, + "mcpexternalauthconfigs": {"server", "virtualMcp"}, +} + +func main() { + sourceDir := flag.String("source", "", "Source directory containing raw CRD YAML files") + targetDir := flag.String("target", "", "Target directory for wrapped Helm templates") + verbose := flag.Bool("verbose", false, "Enable verbose output") + flag.Parse() + + if *sourceDir == "" || *targetDir == "" { + fmt.Fprintln(os.Stderr, "Usage: crd-helm-wrapper -source -target ") + flag.PrintDefaults() + os.Exit(1) + } + + if err := run(*sourceDir, *targetDir, *verbose); err != nil { + fmt.Fprintf(os.Stderr, "โŒ Error: %v\n", err) + os.Exit(1) + } +} + +func run(sourceDir, targetDir string, verbose bool) error { + templates, err := loadTemplates() + if err != nil { + return err + } + + if err := os.MkdirAll(targetDir, 0750); err != nil { + return fmt.Errorf("failed to create target directory: %w", err) + } + + files, err := filepath.Glob(filepath.Join(sourceDir, "*.yaml")) + if err != nil { + return fmt.Errorf("failed to glob source files: %w", err) + } + if len(files) == 0 { + return fmt.Errorf("no YAML files found in %s", sourceDir) + } + + fmt.Printf("๐Ÿ“ฆ Found %d CRD files to process\n", len(files)) + + for _, file := range files { + if err := wrapCRDFile(file, sourceDir, targetDir, templates, verbose); err != nil { + return fmt.Errorf("failed to wrap %s: %w", file, err) + } + } + + fmt.Println("โœ… CRD wrapping completed successfully!") + return nil +} + +func loadTemplates() (map[string]string, error) { + names := []string{"header", "footer", "keep-annotation"} + templates := make(map[string]string, len(names)) + + for _, name := range names { + data, err := templateFS.ReadFile("templates/" + name + ".tpl") + if err != nil { + return nil, fmt.Errorf("failed to load %s template: %w", name, err) + } + templates[name] = string(data) + } + return templates, nil +} + +func wrapCRDFile(sourcePath, sourceDir, targetDir string, templates map[string]string, verbose bool) error { + filename := filepath.Base(sourcePath) + fmt.Printf("Processing: %s\n", filename) + + // Sanitize path to prevent directory traversal + cleanPath := filepath.Clean(sourcePath) + if !strings.HasPrefix(cleanPath, filepath.Clean(sourceDir)) { + return fmt.Errorf("source path escapes source directory: %s", sourcePath) + } + + content, err := os.ReadFile(cleanPath) // #nosec G304 - path is sanitized above + if err != nil { + return fmt.Errorf("failed to read file: %w", err) + } + + crdName, err := extractCRDName(content) + if err != nil { + return fmt.Errorf("failed to extract CRD name: %w", err) + } + + if verbose { + fmt.Printf(" CRD name: %s\n", crdName) + } + + featureFlags, err := getFeatureFlags(crdName) + if err != nil { + return fmt.Errorf("failed to get feature flags: %w", err) + } + if verbose { + fmt.Printf(" Feature flags: %v\n", featureFlags) + } + + wrapped, err := wrapContent(content, templates, featureFlags) + if err != nil { + return fmt.Errorf("failed to wrap content: %w", err) + } + + targetPath := filepath.Join(targetDir, filename) + if err := os.WriteFile(targetPath, wrapped, 0600); err != nil { + return fmt.Errorf("failed to write file: %w", err) + } + + fmt.Printf(" โœ… Created: %s\n", targetPath) + return nil +} + +func extractCRDName(content []byte) (string, error) { + var crd struct { + Kind string `yaml:"kind"` + Metadata struct { + Name string `yaml:"name"` + } `yaml:"metadata"` + } + + if err := yaml.Unmarshal(content, &crd); err != nil { + return "", fmt.Errorf("failed to parse YAML: %w", err) + } + if crd.Kind != "CustomResourceDefinition" { + return "", fmt.Errorf("expected CustomResourceDefinition, got %s", crd.Kind) + } + if crd.Metadata.Name == "" { + return "", fmt.Errorf("CRD name is empty") + } + return crd.Metadata.Name, nil +} + +func getFeatureFlags(crdName string) ([]string, error) { + // CRD names are "plural.group" (e.g., mcpservers.toolhive.stacklok.dev) + idx := strings.Index(crdName, ".") + if idx <= 0 { + return nil, fmt.Errorf("invalid CRD name format: %s", crdName) + } + plural := crdName[:idx] + + flags, ok := crdFeatureFlags[plural] + if !ok { + return nil, fmt.Errorf("CRD %q not in crdFeatureFlags map - please add it", crdName) + } + return flags, nil +} + +func buildFeatureCondition(flags []string) string { + if len(flags) == 0 { + return "" + } + + refs := make([]string, len(flags)) + for i, f := range flags { + refs[i] = ".Values.crds.install." + f + } + + if len(refs) == 1 { + return refs[0] + } + return "or " + strings.Join(refs, " ") +} + +func wrapContent(content []byte, templates map[string]string, featureFlags []string) ([]byte, error) { + var buf bytes.Buffer + + // Write header with feature flag conditional + header := strings.ReplaceAll(templates["header"], "__FEATURE_CONDITION__", buildFeatureCondition(featureFlags)) + buf.WriteString(header) + + // Process YAML content line by line + scanner := bufio.NewScanner(bytes.NewReader(content)) + skipFirstLine := bytes.HasPrefix(content, []byte("---\n")) || bytes.HasPrefix(content, []byte("---\r\n")) + annotationsWritten := false + lineNum := 0 + + for scanner.Scan() { + lineNum++ + line := scanner.Text() + + // Skip document separator on first line + if lineNum == 1 && skipFirstLine && strings.TrimSpace(line) == "---" { + continue + } + + // Inject keep annotation after annotations: line + if strings.TrimSpace(line) == "annotations:" && !annotationsWritten { + buf.WriteString(line + "\n") + buf.WriteString(templates["keep-annotation"]) + annotationsWritten = true + continue + } + + // Escape Go template syntax in CRD descriptions + buf.WriteString(escapeTemplateDelimiters(line) + "\n") + } + + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("error scanning content: %w", err) + } + + buf.WriteString(templates["footer"]) + return buf.Bytes(), nil +} + +// escapeTemplateDelimiters escapes {{ and }} in CRD content to prevent Helm +// from interpreting documentation examples as template directives. +func escapeTemplateDelimiters(line string) string { + if !strings.Contains(line, "{{") { + return line + } + + // Skip our intentional Helm directives + trimmed := strings.TrimSpace(line) + if strings.HasPrefix(trimmed, "{{-") || (strings.HasPrefix(trimmed, "{{") && strings.Contains(trimmed, ".Values")) { + return line + } + + // Escape using Helm's built-in syntax: {{ "{{" }} renders as {{ + line = strings.ReplaceAll(line, "{{", "\x00OPEN\x00") + line = strings.ReplaceAll(line, "}}", "\x00CLOSE\x00") + line = strings.ReplaceAll(line, "\x00OPEN\x00", `{{ "{{" }}`) + line = strings.ReplaceAll(line, "\x00CLOSE\x00", `{{ "}}" }}`) + return line +} diff --git a/deploy/charts/operator-crds/crd-helm-wrapper/templates/footer.tpl b/deploy/charts/operator-crds/crd-helm-wrapper/templates/footer.tpl new file mode 100644 index 000000000..2a42faa9b --- /dev/null +++ b/deploy/charts/operator-crds/crd-helm-wrapper/templates/footer.tpl @@ -0,0 +1 @@ +{{- end }} diff --git a/deploy/charts/operator-crds/crd-helm-wrapper/templates/header.tpl b/deploy/charts/operator-crds/crd-helm-wrapper/templates/header.tpl new file mode 100644 index 000000000..9c06f9d6e --- /dev/null +++ b/deploy/charts/operator-crds/crd-helm-wrapper/templates/header.tpl @@ -0,0 +1 @@ +{{- if __FEATURE_CONDITION__ }} diff --git a/deploy/charts/operator-crds/crd-helm-wrapper/templates/keep-annotation.tpl b/deploy/charts/operator-crds/crd-helm-wrapper/templates/keep-annotation.tpl new file mode 100644 index 000000000..9e7c597ab --- /dev/null +++ b/deploy/charts/operator-crds/crd-helm-wrapper/templates/keep-annotation.tpl @@ -0,0 +1,3 @@ + {{- if .Values.crds.keep }} + helm.sh/resource-policy: keep + {{- end }} diff --git a/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcpexternalauthconfigs.yaml b/deploy/charts/operator-crds/files/crds/toolhive.stacklok.dev_mcpexternalauthconfigs.yaml similarity index 100% rename from deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcpexternalauthconfigs.yaml rename to deploy/charts/operator-crds/files/crds/toolhive.stacklok.dev_mcpexternalauthconfigs.yaml diff --git a/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcpgroups.yaml b/deploy/charts/operator-crds/files/crds/toolhive.stacklok.dev_mcpgroups.yaml similarity index 100% rename from deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcpgroups.yaml rename to deploy/charts/operator-crds/files/crds/toolhive.stacklok.dev_mcpgroups.yaml diff --git a/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcpregistries.yaml b/deploy/charts/operator-crds/files/crds/toolhive.stacklok.dev_mcpregistries.yaml similarity index 100% rename from deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcpregistries.yaml rename to deploy/charts/operator-crds/files/crds/toolhive.stacklok.dev_mcpregistries.yaml diff --git a/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcpremoteproxies.yaml b/deploy/charts/operator-crds/files/crds/toolhive.stacklok.dev_mcpremoteproxies.yaml similarity index 100% rename from deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcpremoteproxies.yaml rename to deploy/charts/operator-crds/files/crds/toolhive.stacklok.dev_mcpremoteproxies.yaml diff --git a/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcpservers.yaml b/deploy/charts/operator-crds/files/crds/toolhive.stacklok.dev_mcpservers.yaml similarity index 100% rename from deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcpservers.yaml rename to deploy/charts/operator-crds/files/crds/toolhive.stacklok.dev_mcpservers.yaml diff --git a/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcptoolconfigs.yaml b/deploy/charts/operator-crds/files/crds/toolhive.stacklok.dev_mcptoolconfigs.yaml similarity index 100% rename from deploy/charts/operator-crds/crds/toolhive.stacklok.dev_mcptoolconfigs.yaml rename to deploy/charts/operator-crds/files/crds/toolhive.stacklok.dev_mcptoolconfigs.yaml diff --git a/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_virtualmcpcompositetooldefinitions.yaml b/deploy/charts/operator-crds/files/crds/toolhive.stacklok.dev_virtualmcpcompositetooldefinitions.yaml similarity index 100% rename from deploy/charts/operator-crds/crds/toolhive.stacklok.dev_virtualmcpcompositetooldefinitions.yaml rename to deploy/charts/operator-crds/files/crds/toolhive.stacklok.dev_virtualmcpcompositetooldefinitions.yaml diff --git a/deploy/charts/operator-crds/crds/toolhive.stacklok.dev_virtualmcpservers.yaml b/deploy/charts/operator-crds/files/crds/toolhive.stacklok.dev_virtualmcpservers.yaml similarity index 100% rename from deploy/charts/operator-crds/crds/toolhive.stacklok.dev_virtualmcpservers.yaml rename to deploy/charts/operator-crds/files/crds/toolhive.stacklok.dev_virtualmcpservers.yaml diff --git a/deploy/charts/operator-crds/templates/toolhive.stacklok.dev_mcpexternalauthconfigs.yaml b/deploy/charts/operator-crds/templates/toolhive.stacklok.dev_mcpexternalauthconfigs.yaml new file mode 100644 index 000000000..cc42b91b0 --- /dev/null +++ b/deploy/charts/operator-crds/templates/toolhive.stacklok.dev_mcpexternalauthconfigs.yaml @@ -0,0 +1,185 @@ +{{- if or .Values.crds.install.server .Values.crds.install.virtualMcp }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + {{- if .Values.crds.keep }} + helm.sh/resource-policy: keep + {{- end }} + controller-gen.kubebuilder.io/version: v0.17.3 + name: mcpexternalauthconfigs.toolhive.stacklok.dev +spec: + group: toolhive.stacklok.dev + names: + kind: MCPExternalAuthConfig + listKind: MCPExternalAuthConfigList + plural: mcpexternalauthconfigs + shortNames: + - extauth + - mcpextauth + singular: mcpexternalauthconfig + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.type + name: Type + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + MCPExternalAuthConfig is the Schema for the mcpexternalauthconfigs API. + MCPExternalAuthConfig resources are namespace-scoped and can only be referenced by + MCPServer resources within the same namespace. Cross-namespace references + are not supported for security and isolation reasons. + 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: |- + MCPExternalAuthConfigSpec defines the desired state of MCPExternalAuthConfig. + MCPExternalAuthConfig resources are namespace-scoped and can only be referenced by + MCPServer resources in the same namespace. + properties: + headerInjection: + description: |- + HeaderInjection configures custom HTTP header injection + Only 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 + required: + - headerName + - valueSecretRef + type: object + tokenExchange: + description: |- + TokenExchange configures RFC-8693 OAuth 2.0 Token Exchange + Only 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 + Optional 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 + Optional 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 + externalTokenHeaderName: + description: |- + ExternalTokenHeaderName is the name of the custom header to use for the exchanged token. + If set, the exchanged token will be added to this custom header (e.g., "X-Upstream-Token"). + If 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 + subjectTokenType: + description: |- + SubjectTokenType is the type of the incoming subject token. + Accepts short forms: "access_token" (default), "id_token", "jwt" + Or full URNs: "urn:ietf:params:oauth:token-type:access_token", + "urn:ietf:params:oauth:token-type:id_token", + "urn:ietf:params:oauth:token-type:jwt" + For 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 + type: + description: Type is the type of external authentication to configure + enum: + - tokenExchange + - headerInjection + - unauthenticated + type: string + required: + - type + type: object + status: + description: MCPExternalAuthConfigStatus defines the observed state of + MCPExternalAuthConfig + properties: + 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. + It corresponds to the MCPExternalAuthConfig's generation, which is updated on mutation by the API Server. + format: int64 + type: integer + referencingServers: + description: |- + ReferencingServers is a list of MCPServer resources that reference this MCPExternalAuthConfig + This helps track which servers need to be reconciled when this config changes + items: + type: string + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} +{{- end }} diff --git a/deploy/charts/operator-crds/templates/toolhive.stacklok.dev_mcpgroups.yaml b/deploy/charts/operator-crds/templates/toolhive.stacklok.dev_mcpgroups.yaml new file mode 100644 index 000000000..6b1c21282 --- /dev/null +++ b/deploy/charts/operator-crds/templates/toolhive.stacklok.dev_mcpgroups.yaml @@ -0,0 +1,138 @@ +{{- if .Values.crds.install.server }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + {{- if .Values.crds.keep }} + helm.sh/resource-policy: keep + {{- end }} + controller-gen.kubebuilder.io/version: v0.17.3 + name: mcpgroups.toolhive.stacklok.dev +spec: + group: toolhive.stacklok.dev + names: + kind: MCPGroup + listKind: MCPGroupList + plural: mcpgroups + shortNames: + - mcpg + - mcpgroup + singular: mcpgroup + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='MCPServersChecked')].status + name: Ready + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: MCPGroup is the Schema for the mcpgroups API + 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: MCPGroupSpec defines the desired state of MCPGroup + properties: + description: + description: Description provides human-readable context + type: string + type: object + 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. + This 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. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with 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. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This 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 + type: array + phase: + default: Pending + description: Phase indicates current state + enum: + - Ready + - Pending + - Failed + type: string + serverCount: + description: ServerCount is the number of servers + type: integer + servers: + description: Servers lists server names in this group + items: + type: string + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} +{{- end }} diff --git a/deploy/charts/operator-crds/templates/toolhive.stacklok.dev_mcpregistries.yaml b/deploy/charts/operator-crds/templates/toolhive.stacklok.dev_mcpregistries.yaml new file mode 100644 index 000000000..7a0e0356b --- /dev/null +++ b/deploy/charts/operator-crds/templates/toolhive.stacklok.dev_mcpregistries.yaml @@ -0,0 +1,561 @@ +{{- if .Values.crds.install.registry }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + {{- if .Values.crds.keep }} + helm.sh/resource-policy: keep + {{- end }} + controller-gen.kubebuilder.io/version: v0.17.3 + name: mcpregistries.toolhive.stacklok.dev +spec: + group: toolhive.stacklok.dev + names: + categories: + - toolhive + kind: MCPRegistry + listKind: MCPRegistryList + plural: mcpregistries + singular: mcpregistry + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.phase + name: Phase + type: string + - jsonPath: .status.syncStatus.phase + name: Sync + type: string + - jsonPath: .status.apiStatus.phase + name: API + type: string + - jsonPath: .status.syncStatus.serverCount + name: Servers + type: integer + - jsonPath: .status.syncStatus.lastSyncTime + name: Last Sync + type: date + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: MCPRegistry is the Schema for the mcpregistries API + 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: MCPRegistrySpec defines the desired state of MCPRegistry + properties: + databaseConfig: + description: |- + DatabaseConfig defines the PostgreSQL database configuration for the registry API server. + If not specified, defaults will be used: + - Host: "postgres" + - Port: 5432 + - User: "db_app" + - MigrationUser: "db_migrator" + - Database: "registry" + - SSLMode: "prefer" + - MaxOpenConns: 10 + - MaxIdleConns: 2 + - ConnMaxLifetime: "30m" + properties: + connMaxLifetime: + default: 30m + description: |- + ConnMaxLifetime is the maximum amount of time a connection may be reused (Go duration format) + Examples: "30m", "1h", "24h" + pattern: ^([0-9]+(\.[0-9]+)?(ns|us|ยตs|ms|s|m|h))+$ + type: string + database: + default: registry + description: Database is the database name + type: string + host: + default: postgres + description: Host is the database server hostname + type: string + maxIdleConns: + default: 2 + description: MaxIdleConns is the maximum number of idle connections + in the pool + minimum: 0 + type: integer + maxOpenConns: + default: 10 + description: MaxOpenConns is the maximum number of open connections + to the database + minimum: 1 + type: integer + migrationUser: + default: db_migrator + description: |- + MigrationUser is the migration user (elevated privileges: CREATE, ALTER, DROP) + Used for running database schema migrations + Credentials should be provided via pgpass file or environment variables + type: string + port: + default: 5432 + description: Port is the database server port + maximum: 65535 + minimum: 1 + type: integer + sslMode: + default: prefer + description: |- + SSLMode is the SSL mode for the connection + Valid values: disable, allow, prefer, require, verify-ca, verify-full + enum: + - disable + - allow + - prefer + - require + - verify-ca + - verify-full + type: string + user: + default: db_app + description: |- + User is the application user (limited privileges: SELECT, INSERT, UPDATE, DELETE) + Credentials should be provided via pgpass file or environment variables + type: string + type: object + displayName: + description: DisplayName is a human-readable name for the registry + type: string + enforceServers: + default: false + description: |- + EnforceServers indicates whether MCPServers in this namespace must have their images + present in at least one registry in the namespace. When any registry in the namespace + has this field set to true, enforcement is enabled for the entire namespace. + MCPServers with images not found in any registry will be rejected. + When false (default), MCPServers can be deployed regardless of registry presence. + type: boolean + podTemplateSpec: + description: |- + PodTemplateSpec defines the pod template to use for the registry API server + This allows for customizing the pod configuration beyond what is provided by the other fields. + Note that to modify the specific container the registry API server runs in, you must specify + the `registry-api` container name in the PodTemplateSpec. + This field accepts a PodTemplateSpec object as JSON/YAML. + type: object + x-kubernetes-preserve-unknown-fields: true + registries: + description: Registries defines the configuration for the registry + data sources + items: + description: MCPRegistryConfig defines the configuration for a registry + data source + properties: + api: + description: |- + API defines the API source configuration + Mutually exclusive with ConfigMapRef, Git, and PVCRef + properties: + endpoint: + description: |- + Endpoint is the base API URL (without path) + The controller will append the appropriate paths: + Phase 1 (ToolHive API): + - /v0/servers - List all servers (single response, no pagination) + - /v0/servers/{name} - Get specific server (future) + - /v0/info - Get registry metadata (future) + Example: "http://my-registry-api.default.svc.cluster.local/api" + minLength: 1 + pattern: ^https?://.* + type: string + required: + - endpoint + type: object + configMapRef: + description: |- + ConfigMapRef defines the ConfigMap source configuration + Mutually exclusive with Git, API, and PVCRef + properties: + key: + description: The key to select. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More 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 + filter: + description: Filter defines include/exclude patterns for registry + content + properties: + names: + description: NameFilters defines name-based filtering + properties: + exclude: + description: Exclude is a list of glob patterns to exclude + items: + type: string + type: array + include: + description: Include is a list of glob patterns to include + items: + type: string + type: array + type: object + tags: + description: Tags defines tag-based filtering + properties: + exclude: + description: Exclude is a list of tags to exclude + items: + type: string + type: array + include: + description: Include is a list of tags to include + items: + type: string + type: array + type: object + type: object + format: + default: toolhive + description: Format is the data format (toolhive, upstream) + enum: + - toolhive + - upstream + type: string + git: + description: |- + Git defines the Git repository source configuration + Mutually exclusive with ConfigMapRef, API, and PVCRef + properties: + branch: + description: Branch is the Git branch to use (mutually exclusive + with Tag and Commit) + minLength: 1 + type: string + commit: + description: Commit is the Git commit SHA to use (mutually + exclusive with Branch and Tag) + minLength: 1 + type: string + path: + default: registry.json + description: Path is the path to the registry file within + the repository + pattern: ^.*\.json$ + type: string + repository: + description: Repository is the Git repository URL (HTTP/HTTPS/SSH) + minLength: 1 + pattern: ^(file:///|https?://|git@|ssh://|git://).* + type: string + tag: + description: Tag is the Git tag to use (mutually exclusive + with Branch and Commit) + minLength: 1 + type: string + required: + - repository + type: object + name: + description: Name is a unique identifier for this registry configuration + within the MCPRegistry + minLength: 1 + type: string + pvcRef: + description: |- + PVCRef defines the PersistentVolumeClaim source configuration + Mutually exclusive with ConfigMapRef, Git, and API + properties: + claimName: + description: ClaimName is the name of the PersistentVolumeClaim + minLength: 1 + type: string + path: + default: registry.json + description: |- + Path is the relative path to the registry file within the PVC. + The PVC is mounted at /config/registry/{registryName}/. + The full file path becomes: /config/registry/{registryName}/{path} + + This design: + - Each registry gets its own mount point (consistent with ConfigMap sources) + - Multiple registries can share the same PVC by mounting it at different paths + - Users control PVC organization freely via the path field + + Examples: + Registry "production" using PVC "shared-data" with path "prod/registry.json": + PVC contains /prod/registry.json โ†’ accessed at /config/registry/production/prod/registry.json + + Registry "development" using SAME PVC "shared-data" with path "dev/registry.json": + PVC contains /dev/registry.json โ†’ accessed at /config/registry/development/dev/registry.json + (Same PVC, different mount path) + + Registry "staging" using DIFFERENT PVC "other-pvc" with path "registry.json": + PVC contains /registry.json โ†’ accessed at /config/registry/staging/registry.json + (Different PVC, independent mount) + + Registry "team-a" with path "v1/servers.json": + PVC contains /v1/servers.json โ†’ accessed at /config/registry/team-a/v1/servers.json + (Subdirectories allowed in path) + pattern: ^.*\.json$ + type: string + required: + - claimName + type: object + syncPolicy: + description: |- + SyncPolicy defines the automatic synchronization behavior for this registry. + If specified, enables automatic synchronization at the given interval. + Manual synchronization is always supported via annotation-based triggers + regardless of this setting. + properties: + interval: + description: |- + Interval is the sync interval for automatic synchronization (Go duration format) + Examples: "1h", "30m", "24h" + pattern: ^([0-9]+(\.[0-9]+)?(ns|us|ยตs|ms|s|m|h))+$ + type: string + required: + - interval + type: object + required: + - name + type: object + minItems: 1 + type: array + required: + - registries + type: object + status: + description: MCPRegistryStatus defines the observed state of MCPRegistry + properties: + apiStatus: + description: APIStatus provides detailed information about the API + service + properties: + endpoint: + description: Endpoint is the URL where the API is accessible + type: string + message: + description: Message provides additional information about the + API status + type: string + phase: + allOf: + - enum: + - NotStarted + - Deploying + - Ready + - Unhealthy + - Error + - enum: + - NotStarted + - Deploying + - Ready + - Unhealthy + - Error + description: Phase represents the current API service phase + type: string + readySince: + description: ReadySince is the timestamp when the API became ready + format: date-time + type: string + required: + - phase + type: object + 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. + This 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. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with 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. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This 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 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + lastAppliedFilterHash: + description: LastAppliedFilterHash is the hash of the last applied + filter + type: string + lastManualSyncTrigger: + description: |- + LastManualSyncTrigger tracks the last processed manual sync annotation value + Used to detect new manual sync requests via toolhive.stacklok.dev/sync-trigger annotation + type: string + message: + description: Message provides additional information about the current + phase + type: string + phase: + description: |- + Phase represents the current overall phase of the MCPRegistry + Derived from sync and API status + enum: + - Pending + - Ready + - Failed + - Syncing + - Terminating + type: string + storageRef: + description: StorageRef is a reference to the internal storage location + properties: + configMapRef: + description: |- + ConfigMapRef is a reference to a ConfigMap storage + Only used when Type is "configmap" + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: + description: Type is the storage type (configmap) + enum: + - configmap + type: string + required: + - type + type: object + syncStatus: + description: SyncStatus provides detailed information about data synchronization + properties: + attemptCount: + description: AttemptCount is the number of sync attempts since + last success + minimum: 0 + type: integer + lastAttempt: + description: LastAttempt is the timestamp of the last sync attempt + format: date-time + type: string + lastSyncHash: + description: |- + LastSyncHash is the hash of the last successfully synced data + Used to detect changes in source data + type: string + lastSyncTime: + description: LastSyncTime is the timestamp of the last successful + sync + format: date-time + type: string + message: + description: Message provides additional information about the + sync status + type: string + phase: + allOf: + - enum: + - Syncing + - Complete + - Failed + - enum: + - Syncing + - Complete + - Failed + description: Phase represents the current synchronization phase + type: string + serverCount: + description: ServerCount is the total number of servers in the + registry + minimum: 0 + type: integer + required: + - phase + type: object + type: object + type: object + x-kubernetes-validations: + - message: at least one registry must be specified + rule: size(self.spec.registries) > 0 + served: true + storage: true + subresources: + status: {} +{{- end }} diff --git a/deploy/charts/operator-crds/templates/toolhive.stacklok.dev_mcpremoteproxies.yaml b/deploy/charts/operator-crds/templates/toolhive.stacklok.dev_mcpremoteproxies.yaml new file mode 100644 index 000000000..7abf58da1 --- /dev/null +++ b/deploy/charts/operator-crds/templates/toolhive.stacklok.dev_mcpremoteproxies.yaml @@ -0,0 +1,593 @@ +{{- if .Values.crds.install.server }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + {{- if .Values.crds.keep }} + helm.sh/resource-policy: keep + {{- end }} + controller-gen.kubebuilder.io/version: v0.17.3 + name: mcpremoteproxies.toolhive.stacklok.dev +spec: + group: toolhive.stacklok.dev + names: + kind: MCPRemoteProxy + listKind: MCPRemoteProxyList + plural: mcpremoteproxies + singular: mcpremoteproxy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.phase + name: Phase + type: string + - jsonPath: .spec.remoteURL + name: Remote URL + type: string + - jsonPath: .status.url + name: URL + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + MCPRemoteProxy is the Schema for the mcpremoteproxies API + It 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. + 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: 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 + When true, enables audit logging with default configuration + type: boolean + type: object + authzConfig: + description: AuthzConfig defines authorization policy configuration + for the proxy + properties: + configMap: + description: |- + ConfigMap references a ConfigMap containing authorization configuration + Only 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 + inline: + description: |- + Inline contains direct authorization configuration + Only used when Type is "inline" + properties: + entitiesJson: + default: '[]' + description: EntitiesJSON is a JSON string representing Cedar + entities + type: string + policies: + description: Policies is a list of Cedar policy strings + items: + type: string + minItems: 1 + type: array + required: + - policies + type: object + type: + default: configMap + description: Type is the type of authorization configuration + enum: + - configMap + - inline + type: string + required: + - type + type: object + externalAuthConfigRef: + description: |- + ExternalAuthConfigRef references a MCPExternalAuthConfig resource for token exchange. + When specified, the proxy will exchange validated incoming tokens for remote service tokens. + The 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 + oidcConfig: + description: |- + OIDCConfig defines OIDC authentication configuration for the proxy + This validates incoming tokens from clients. Required for proxy mode. + properties: + configMap: + description: |- + ConfigMap references a ConfigMap containing OIDC configuration + Only used when Type is "configmap" + properties: + key: + default: oidc.json + description: Key is the key in the ConfigMap that contains + the OIDC configuration + type: string + name: + description: Name is the name of the ConfigMap + type: string + required: + - name + type: object + inline: + description: |- + Inline contains direct OIDC configuration + Only used when Type is "inline" + properties: + audience: + description: Audience is the expected audience for the token + type: string + clientId: + description: ClientID is the OIDC client ID + type: string + clientSecret: + description: |- + ClientSecret is the client secret for introspection (optional) + Deprecated: Use ClientSecretRef instead for better security + type: string + clientSecretRef: + description: |- + ClientSecretRef is a reference to a Kubernetes Secret containing the client secret + If both ClientSecret and ClientSecretRef are provided, ClientSecretRef takes precedence + 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 + insecureAllowHTTP: + default: false + description: |- + InsecureAllowHTTP allows HTTP (non-HTTPS) OIDC issuers for development/testing + WARNING: This is insecure and should NEVER be used in production + Only enable for local development, testing, or trusted internal networks + 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 + Use with caution - only enable for trusted internal IDPs + type: boolean + jwksAuthTokenPath: + description: |- + JWKSAuthTokenPath is the path to file containing bearer token for JWKS/OIDC requests + The file must be mounted into the pod (e.g., via Secret volume) + 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 + Use with caution - only enable for trusted internal IDPs or testing + type: boolean + thvCABundlePath: + description: |- + ThvCABundlePath is the path to CA certificate bundle file for HTTPS requests + The file must be mounted into the pod (e.g., via ConfigMap or Secret volume) + type: string + required: + - issuer + type: object + kubernetes: + description: |- + Kubernetes configures OIDC for Kubernetes service account token validation + Only used when Type is "kubernetes" + properties: + audience: + default: toolhive + description: Audience is the expected audience for the token + type: string + introspectionUrl: + description: |- + IntrospectionURL is the URL for token introspection endpoint + If 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 + If empty, OIDC discovery will be used to automatically determine the JWKS URL + type: string + namespace: + description: |- + Namespace is the namespace of the service account + If empty, uses the MCPServer's namespace + type: string + serviceAccount: + description: |- + ServiceAccount is the name of the service account to validate tokens for + If empty, uses the pod's service account + type: string + useClusterAuth: + description: |- + UseClusterAuth enables using the Kubernetes cluster's CA bundle and service account token + When true, uses /var/run/secrets/kubernetes.io/serviceaccount/ca.crt for TLS verification + and /var/run/secrets/kubernetes.io/serviceaccount/token for bearer token authentication + Defaults to true if not specified + type: boolean + type: object + resourceUrl: + description: |- + ResourceURL is the explicit resource URL for OAuth discovery endpoint (RFC 9728) + If not specified, defaults to the in-cluster Kubernetes service URL + type: string + type: + default: kubernetes + description: Type is the type of OIDC configuration + enum: + - kubernetes + - configMap + - inline + type: string + required: + - type + type: object + port: + default: 8080 + description: Port 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) + These affect the toolhive proxy itself, not the MCP server it manages + Use 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 + type: array + 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 + type: object + 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 + type: object + 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 + 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 + type: object + telemetry: + description: Telemetry defines observability configuration for the + proxy + properties: + openTelemetry: + description: OpenTelemetry defines OpenTelemetry configuration + properties: + 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: + description: |- + Headers contains authentication headers for the OTLP endpoint + Specified as key=value pairs + items: + type: string + type: array + 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 + serviceName: + description: |- + ServiceName is the service name for telemetry + If not specified, defaults to the MCPServer name + type: string + 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) + type: string + type: object + type: object + prometheus: + description: Prometheus defines Prometheus-specific configuration + properties: + enabled: + default: false + description: Enabled controls whether Prometheus metrics endpoint + is exposed + type: boolean + type: object + type: object + toolConfigRef: + description: |- + ToolConfigRef references a MCPToolConfig resource for tool filtering and renaming. + The referenced MCPToolConfig must exist in the same namespace as this MCPRemoteProxy. + Cross-namespace references are not supported for security and isolation reasons. + If 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 + 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 + When enabled, the proxy will use X-Forwarded-Proto, X-Forwarded-Host, X-Forwarded-Port, + and X-Forwarded-Prefix headers to construct endpoint URLs + type: boolean + required: + - oidcConfig + - remoteURL + type: object + status: + description: MCPRemoteProxyStatus defines the observed state of MCPRemoteProxy + properties: + 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. + This 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. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with 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. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This 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 + type: array + 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 + phase: + description: Phase is the current phase of the MCPRemoteProxy + enum: + - Pending + - Ready + - Failed + - Terminating + 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 + type: object + served: true + storage: true + subresources: + status: {} +{{- end }} diff --git a/deploy/charts/operator-crds/templates/toolhive.stacklok.dev_mcpservers.yaml b/deploy/charts/operator-crds/templates/toolhive.stacklok.dev_mcpservers.yaml new file mode 100644 index 000000000..703c50a7f --- /dev/null +++ b/deploy/charts/operator-crds/templates/toolhive.stacklok.dev_mcpservers.yaml @@ -0,0 +1,729 @@ +{{- if .Values.crds.install.server }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + {{- if .Values.crds.keep }} + helm.sh/resource-policy: keep + {{- end }} + controller-gen.kubebuilder.io/version: v0.17.3 + name: mcpservers.toolhive.stacklok.dev +spec: + group: toolhive.stacklok.dev + names: + kind: MCPServer + listKind: MCPServerList + plural: mcpservers + singular: mcpserver + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.phase + name: Status + type: string + - jsonPath: .status.url + name: URL + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: MCPServer is the Schema for the mcpservers API + 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: 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 + audit: + description: Audit defines audit logging configuration for the MCP + server + properties: + enabled: + default: false + description: |- + Enabled controls whether audit logging is enabled + When true, enables audit logging with default configuration + type: boolean + type: object + authzConfig: + description: AuthzConfig defines authorization policy configuration + for the MCP server + properties: + configMap: + description: |- + ConfigMap references a ConfigMap containing authorization configuration + Only 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 + inline: + description: |- + Inline contains direct authorization configuration + Only used when Type is "inline" + properties: + entitiesJson: + default: '[]' + description: EntitiesJSON is a JSON string representing Cedar + entities + type: string + policies: + description: Policies is a list of Cedar policy strings + items: + type: string + minItems: 1 + type: array + required: + - policies + type: object + type: + default: configMap + description: Type is the type of authorization configuration + enum: + - configMap + - inline + type: string + required: + - type + type: object + 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 + type: array + externalAuthConfigRef: + description: |- + ExternalAuthConfigRef references a MCPExternalAuthConfig resource for external authentication. + The 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 + groupRef: + description: |- + GroupRef is the name of the MCPGroup this server belongs to + Must reference an existing MCPGroup in the same namespace + type: string + 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 + oidcConfig: + description: OIDCConfig defines OIDC authentication configuration + for the MCP server + properties: + configMap: + description: |- + ConfigMap references a ConfigMap containing OIDC configuration + Only used when Type is "configmap" + properties: + key: + default: oidc.json + description: Key is the key in the ConfigMap that contains + the OIDC configuration + type: string + name: + description: Name is the name of the ConfigMap + type: string + required: + - name + type: object + inline: + description: |- + Inline contains direct OIDC configuration + Only used when Type is "inline" + properties: + audience: + description: Audience is the expected audience for the token + type: string + clientId: + description: ClientID is the OIDC client ID + type: string + clientSecret: + description: |- + ClientSecret is the client secret for introspection (optional) + Deprecated: Use ClientSecretRef instead for better security + type: string + clientSecretRef: + description: |- + ClientSecretRef is a reference to a Kubernetes Secret containing the client secret + If both ClientSecret and ClientSecretRef are provided, ClientSecretRef takes precedence + 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 + insecureAllowHTTP: + default: false + description: |- + InsecureAllowHTTP allows HTTP (non-HTTPS) OIDC issuers for development/testing + WARNING: This is insecure and should NEVER be used in production + Only enable for local development, testing, or trusted internal networks + 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 + Use with caution - only enable for trusted internal IDPs + type: boolean + jwksAuthTokenPath: + description: |- + JWKSAuthTokenPath is the path to file containing bearer token for JWKS/OIDC requests + The file must be mounted into the pod (e.g., via Secret volume) + 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 + Use with caution - only enable for trusted internal IDPs or testing + type: boolean + thvCABundlePath: + description: |- + ThvCABundlePath is the path to CA certificate bundle file for HTTPS requests + The file must be mounted into the pod (e.g., via ConfigMap or Secret volume) + type: string + required: + - issuer + type: object + kubernetes: + description: |- + Kubernetes configures OIDC for Kubernetes service account token validation + Only used when Type is "kubernetes" + properties: + audience: + default: toolhive + description: Audience is the expected audience for the token + type: string + introspectionUrl: + description: |- + IntrospectionURL is the URL for token introspection endpoint + If 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 + If empty, OIDC discovery will be used to automatically determine the JWKS URL + type: string + namespace: + description: |- + Namespace is the namespace of the service account + If empty, uses the MCPServer's namespace + type: string + serviceAccount: + description: |- + ServiceAccount is the name of the service account to validate tokens for + If empty, uses the pod's service account + type: string + useClusterAuth: + description: |- + UseClusterAuth enables using the Kubernetes cluster's CA bundle and service account token + When true, uses /var/run/secrets/kubernetes.io/serviceaccount/ca.crt for TLS verification + and /var/run/secrets/kubernetes.io/serviceaccount/token for bearer token authentication + Defaults to true if not specified + type: boolean + type: object + resourceUrl: + description: |- + ResourceURL is the explicit resource URL for OAuth discovery endpoint (RFC 9728) + If not specified, defaults to the in-cluster Kubernetes service URL + type: string + type: + default: kubernetes + description: Type is the type of OIDC configuration + enum: + - kubernetes + - configMap + - inline + type: string + required: + - type + type: object + permissionProfile: + description: PermissionProfile defines the permission profile to use + properties: + key: + description: |- + Key is the key in the ConfigMap that contains the permission profile + Only used when Type is "configmap" + type: string + name: + description: |- + Name is the name of the permission profile + If Type is "builtin", Name must be one of: "none", "network" + If 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 + podTemplateSpec: + description: |- + PodTemplateSpec defines the pod template to use for the MCP server + This allows for customizing the pod configuration beyond what is provided by the other fields. + Note that to modify the specific container the MCP server runs in, you must specify + the `mcp` container name in the PodTemplateSpec. + This field accepts a PodTemplateSpec object as JSON/YAML. + type: object + x-kubernetes-preserve-unknown-fields: true + port: + default: 8080 + description: |- + Port is the port to expose the MCP server on + Deprecated: Use ProxyPort instead + format: int32 + maximum: 65535 + minimum: 1 + type: integer + proxyMode: + default: streamable-http + description: |- + ProxyMode is the proxy mode for stdio transport (sse or streamable-http) + This setting is only used when Transport is "stdio" + 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 + 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) + These affect the toolhive proxy itself, not the MCP server it manages + Use 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 + type: array + 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 + type: object + 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 + type: object + 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 + 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 + type: object + 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 + If left unspecified, it defaults to the key + type: string + required: + - key + - name + type: object + type: array + serviceAccount: + description: |- + ServiceAccount is the name of an already existing service account to use by the MCP server. + If not specified, a ServiceAccount will be created automatically and used by the MCP server. + type: string + targetPort: + description: |- + TargetPort is the port that MCP server listens to + Deprecated: Use McpPort instead + format: int32 + maximum: 65535 + minimum: 1 + type: integer + telemetry: + description: Telemetry defines observability configuration for the + MCP server + properties: + openTelemetry: + description: OpenTelemetry defines OpenTelemetry configuration + properties: + 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: + description: |- + Headers contains authentication headers for the OTLP endpoint + Specified as key=value pairs + items: + type: string + type: array + 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 + serviceName: + description: |- + ServiceName is the service name for telemetry + If not specified, defaults to the MCPServer name + type: string + 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) + type: string + type: object + type: object + prometheus: + description: Prometheus defines Prometheus-specific configuration + properties: + enabled: + default: false + description: Enabled controls whether Prometheus metrics endpoint + is exposed + type: boolean + type: object + type: object + toolConfigRef: + description: |- + ToolConfigRef references a MCPToolConfig resource for tool filtering and renaming. + The referenced MCPToolConfig must exist in the same namespace as this MCPServer. + Cross-namespace references are not supported for security and isolation reasons. + If specified, this takes precedence over the inline ToolsFilter field. + properties: + name: + description: Name is the name of the MCPToolConfig resource in + the same namespace + type: string + required: + - name + type: object + tools: + description: |- + ToolsFilter is the filter on tools applied to the MCP server + Deprecated: Use ToolConfigRef instead + items: + type: string + type: array + 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 + When enabled, the proxy will use X-Forwarded-Proto, X-Forwarded-Host, X-Forwarded-Port, + and 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 + type: array + required: + - image + type: object + status: + description: MCPServerStatus defines the observed state of MCPServer + properties: + 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. + This 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. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with 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. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This 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 + type: array + 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 + phase: + description: Phase is the current phase of the MCPServer + enum: + - Pending + - Running + - Failed + - Terminating + 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 + type: object + type: object + served: true + storage: true + subresources: + status: {} +{{- end }} diff --git a/deploy/charts/operator-crds/templates/toolhive.stacklok.dev_mcptoolconfigs.yaml b/deploy/charts/operator-crds/templates/toolhive.stacklok.dev_mcptoolconfigs.yaml new file mode 100644 index 000000000..4fefaf223 --- /dev/null +++ b/deploy/charts/operator-crds/templates/toolhive.stacklok.dev_mcptoolconfigs.yaml @@ -0,0 +1,122 @@ +{{- if .Values.crds.install.server }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + {{- if .Values.crds.keep }} + helm.sh/resource-policy: keep + {{- end }} + controller-gen.kubebuilder.io/version: v0.17.3 + name: mcptoolconfigs.toolhive.stacklok.dev +spec: + group: toolhive.stacklok.dev + names: + kind: MCPToolConfig + listKind: MCPToolConfigList + plural: mcptoolconfigs + shortNames: + - tc + - toolconfig + singular: mcptoolconfig + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.toolsFilter[*] + name: Filter Count + type: integer + - jsonPath: .spec.toolsOverride + name: Override Count + type: integer + - jsonPath: .status.referencingServers + name: Referenced By + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + MCPToolConfig is the Schema for the mcptoolconfigs API. + MCPToolConfig resources are namespace-scoped and can only be referenced by + MCPServer resources within the same namespace. Cross-namespace references + are not supported for security and isolation reasons. + 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: |- + MCPToolConfigSpec defines the desired state of MCPToolConfig. + MCPToolConfig resources are namespace-scoped and can only be referenced by + MCPServer resources in the same namespace. + properties: + toolsFilter: + description: |- + ToolsFilter is a list of tool names to filter (allow list). + Only tools in this list will be exposed by the MCP server. + If empty, all tools are exposed. + items: + type: string + type: array + toolsOverride: + additionalProperties: + description: |- + ToolOverride represents a tool override configuration. + Both Name and Description can be overridden independently, but + they can't be both empty. + properties: + 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 + description: |- + ToolsOverride is a map from actual tool names to their overridden configuration. + This allows renaming tools and/or changing their descriptions. + type: object + type: object + status: + description: MCPToolConfigStatus defines the observed state of MCPToolConfig + properties: + 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. + It corresponds to the MCPToolConfig's generation, which is updated on mutation by the API Server. + format: int64 + type: integer + referencingServers: + description: |- + ReferencingServers is a list of MCPServer resources that reference this MCPToolConfig + This helps track which servers need to be reconciled when this config changes + items: + type: string + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} +{{- end }} diff --git a/deploy/charts/operator-crds/templates/toolhive.stacklok.dev_virtualmcpcompositetooldefinitions.yaml b/deploy/charts/operator-crds/templates/toolhive.stacklok.dev_virtualmcpcompositetooldefinitions.yaml new file mode 100644 index 000000000..0a2876880 --- /dev/null +++ b/deploy/charts/operator-crds/templates/toolhive.stacklok.dev_virtualmcpcompositetooldefinitions.yaml @@ -0,0 +1,393 @@ +{{- if .Values.crds.install.virtualMcp }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + {{- if .Values.crds.keep }} + helm.sh/resource-policy: keep + {{- end }} + controller-gen.kubebuilder.io/version: v0.17.3 + name: virtualmcpcompositetooldefinitions.toolhive.stacklok.dev +spec: + group: toolhive.stacklok.dev + names: + kind: VirtualMCPCompositeToolDefinition + listKind: VirtualMCPCompositeToolDefinitionList + plural: virtualmcpcompositetooldefinitions + shortNames: + - vmcpctd + - compositetool + singular: virtualmcpcompositetooldefinition + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Workflow name + jsonPath: .spec.name + name: Workflow + type: string + - description: Number of steps + jsonPath: .spec.steps[*] + name: Steps + type: integer + - description: Validation status + jsonPath: .status.validationStatus + name: Status + type: string + - description: Refs + jsonPath: .status.referencingVirtualServers[*] + name: Refs + type: integer + - description: Age + jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: Ready + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + VirtualMCPCompositeToolDefinition is the Schema for the virtualmcpcompositetooldefinitions API + VirtualMCPCompositeToolDefinition defines reusable composite workflows that can be referenced + by multiple VirtualMCPServer instances + 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: VirtualMCPCompositeToolDefinitionSpec defines the desired + state of VirtualMCPCompositeToolDefinition + properties: + description: + description: Description is a human-readable description of the workflow + minLength: 1 + type: string + failureMode: + default: abort + description: |- + FailureMode defines the failure handling strategy + - abort: Stop execution on first failure (default) + - continue: Continue executing remaining steps + enum: + - abort + - continue + type: string + name: + description: Name is the workflow name exposed as a composite tool + maxLength: 64 + minLength: 1 + pattern: ^[a-z0-9]([a-z0-9_-]*[a-z0-9])?$ + type: string + output: + description: |- + Output defines the structured output schema for the composite tool. + Specifies how to construct the final output from workflow step results. + If not specified, the workflow returns the last step's output (backward compatible). + properties: + properties: + additionalProperties: + description: OutputPropertySpec defines a single output property + properties: + default: + description: Default is the fallback value if template expansion + fails + 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 + 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 + Supports template syntax: {{ "{{" }}.steps.step_id.output.field{{ "}}" }}, {{ "{{" }}.params.param_name{{ "}}" }} + For object types, this can be a JSON string that will be deserialized + type: string + required: + - type + type: object + description: |- + Properties defines the output properties + Map 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 + type: object + parameters: + description: |- + Parameters defines the input parameter schema for the workflow in JSON Schema format. + Should be a JSON Schema object with "type": "object" and "properties". + Per MCP specification, this should follow standard JSON Schema for tool inputSchema. + Example: + { + "type": "object", + "properties": { + "param1": {"type": "string", "default": "value"}, + "param2": {"type": "integer"} + }, + "required": ["param2"] + } + type: object + x-kubernetes-preserve-unknown-fields: true + steps: + description: |- + Steps defines the workflow step definitions + Steps are executed sequentially in Phase 1 + Phase 2 will support DAG execution via dependsOn + items: + description: WorkflowStep defines a step in a composite tool workflow + properties: + arguments: + description: |- + Arguments is a map of argument values with template expansion support. + Supports Go template syntax with .params and .steps for string values. + Non-string values (integers, booleans, arrays, objects) are passed as-is. + Note: the templating is only supported on the first level of the key-value pairs. + type: object + x-kubernetes-preserve-unknown-fields: true + condition: + description: Condition is a template expression that determines + if the step should execute + type: string + 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 + message: + description: |- + Message is the elicitation message + Only used when Type is "elicitation" + type: string + onCancel: + description: |- + OnCancel defines the action to take when the user cancels/dismisses the elicitation + Only used when Type is "elicitation" + properties: + action: + default: abort + description: |- + Action defines the action to take when the user declines or cancels + - skip_remaining: Skip remaining steps in the workflow + - abort: Abort the entire workflow execution + - continue: Continue to the next step + enum: + - skip_remaining + - abort + - continue + type: string + type: object + onDecline: + description: |- + OnDecline defines the action to take when the user explicitly declines the elicitation + Only used when Type is "elicitation" + properties: + action: + default: abort + description: |- + Action defines the action to take when the user declines or cancels + - skip_remaining: Skip remaining steps in the workflow + - abort: Abort the entire workflow execution + - continue: Continue to the next step + enum: + - skip_remaining + - abort + - continue + type: string + type: object + 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 + maxRetries: + description: |- + MaxRetries is the maximum number of retries + Only used when Action is "retry" + type: integer + retryDelay: + description: |- + RetryDelay is the delay between retry attempts + Only used when Action is "retry" + pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m))+$ + type: string + type: object + schema: + description: Schema defines the expected response schema for + elicitation + type: object + x-kubernetes-preserve-unknown-fields: true + timeout: + description: Timeout is the maximum execution time for this + step + type: string + tool: + description: |- + Tool is the tool to call (format: "workload.tool_name") + Only used when Type is "tool" + type: string + type: + default: tool + description: Type is the step type (tool, elicitation, etc.) + enum: + - tool + - elicitation + type: string + required: + - id + type: object + minItems: 1 + type: array + timeout: + default: 30m + description: |- + Timeout is the overall workflow timeout + Defaults to 30m if not specified + pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ + type: string + required: + - description + - name + - steps + type: object + 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. + This 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. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with 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. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This 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 + type: array + observedGeneration: + description: |- + ObservedGeneration is the most recent generation observed for this VirtualMCPCompositeToolDefinition + It 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 + This helps track which servers need to be reconciled when this workflow changes + items: + type: string + type: array + validationErrors: + description: ValidationErrors contains validation error messages if + ValidationStatus is Invalid + items: + type: string + type: array + validationStatus: + description: |- + ValidationStatus indicates the validation state of the workflow + - Valid: Workflow structure is valid + - Invalid: Workflow has validation errors + enum: + - Valid + - Invalid + - Unknown + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +{{- end }} diff --git a/deploy/charts/operator-crds/templates/toolhive.stacklok.dev_virtualmcpservers.yaml b/deploy/charts/operator-crds/templates/toolhive.stacklok.dev_virtualmcpservers.yaml new file mode 100644 index 000000000..6125fe9fd --- /dev/null +++ b/deploy/charts/operator-crds/templates/toolhive.stacklok.dev_virtualmcpservers.yaml @@ -0,0 +1,971 @@ +{{- if .Values.crds.install.virtualMcp }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + {{- if .Values.crds.keep }} + helm.sh/resource-policy: keep + {{- end }} + controller-gen.kubebuilder.io/version: v0.17.3 + name: virtualmcpservers.toolhive.stacklok.dev +spec: + group: toolhive.stacklok.dev + names: + kind: VirtualMCPServer + listKind: VirtualMCPServerList + plural: virtualmcpservers + shortNames: + - vmcp + - virtualmcp + singular: virtualmcpserver + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The phase of the VirtualMCPServer + jsonPath: .status.phase + name: Phase + type: string + - description: Virtual MCP server URL + jsonPath: .status.url + name: URL + type: string + - description: Discovered backends count + jsonPath: .status.backendCount + name: Backends + type: integer + - description: Age + jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: Ready + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + VirtualMCPServer is the Schema for the virtualmcpservers API + VirtualMCPServer aggregates multiple backend MCPServers into a unified endpoint + 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: VirtualMCPServerSpec defines the desired state of VirtualMCPServer + properties: + aggregation: + description: Aggregation defines tool aggregation and conflict resolution + strategies + properties: + conflictResolution: + default: prefix + description: |- + ConflictResolution defines the strategy for resolving tool name conflicts + - prefix: Automatically prefix tool names with workload identifier + - priority: First workload in priority order wins + - 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 + Supports 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 + tools: + description: |- + Tools defines per-workload tool filtering and overrides + References existing MCPToolConfig resources + items: + description: WorkloadToolConfig defines tool filtering and overrides + for a specific workload + properties: + filter: + description: |- + Filter is an inline list of tool names to allow (allow list) + Only used if ToolConfigRef is not specified + items: + type: string + type: array + overrides: + additionalProperties: + description: |- + ToolOverride represents a tool override configuration. + Both Name and Description can be overridden independently, but + they can't be both empty. + properties: + 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 + description: |- + Overrides is an inline map of tool overrides + Only used if ToolConfigRef is not specified + type: object + toolConfigRef: + description: |- + ToolConfigRef references a MCPToolConfig resource for tool filtering and renaming + If specified, Filter and Overrides are ignored + properties: + name: + description: Name is the name of the MCPToolConfig resource + in the same namespace + type: string + required: + - name + type: object + workload: + description: Workload is the name of the backend MCPServer + workload + type: string + required: + - workload + type: object + type: array + type: object + compositeToolRefs: + description: |- + CompositeToolRefs references VirtualMCPCompositeToolDefinition resources + for complex, reusable workflows + items: + description: CompositeToolDefinitionRef references a VirtualMCPCompositeToolDefinition + resource + properties: + name: + description: Name is the name of the VirtualMCPCompositeToolDefinition + resource in the same namespace + type: string + required: + - name + type: object + type: array + compositeTools: + description: |- + CompositeTools defines inline composite tool definitions + For complex workflows, reference VirtualMCPCompositeToolDefinition resources instead + items: + description: |- + CompositeToolSpec defines an inline composite tool + For complex workflows, reference VirtualMCPCompositeToolDefinition resources instead + properties: + description: + description: Description describes the composite tool + type: string + name: + description: Name is the name of the composite tool + type: string + output: + description: |- + Output defines the structured output schema for the composite tool. + Specifies how to construct the final output from workflow step results. + If not specified, the workflow returns the last step's output (backward compatible). + properties: + properties: + additionalProperties: + description: OutputPropertySpec defines a single output + property + properties: + default: + description: Default is the fallback value if template + expansion fails + 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 + 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 + Supports template syntax: {{ "{{" }}.steps.step_id.output.field{{ "}}" }}, {{ "{{" }}.params.param_name{{ "}}" }} + For object types, this can be a JSON string that will be deserialized + type: string + required: + - type + type: object + description: |- + Properties defines the output properties + Map 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 + type: object + parameters: + description: |- + Parameters defines the input parameter schema in JSON Schema format. + Should be a JSON Schema object with "type": "object" and "properties". + Per MCP specification, this should follow standard JSON Schema for tool inputSchema. + Example: + { + "type": "object", + "properties": { + "param1": {"type": "string", "default": "value"}, + "param2": {"type": "integer"} + }, + "required": ["param2"] + } + type: object + x-kubernetes-preserve-unknown-fields: true + steps: + description: Steps defines the workflow steps + items: + description: WorkflowStep defines a step in a composite tool + workflow + properties: + arguments: + description: |- + Arguments is a map of argument values with template expansion support. + Supports Go template syntax with .params and .steps for string values. + Non-string values (integers, booleans, arrays, objects) are passed as-is. + Note: the templating is only supported on the first level of the key-value pairs. + type: object + x-kubernetes-preserve-unknown-fields: true + condition: + description: Condition is a template expression that determines + if the step should execute + type: string + 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 + message: + description: |- + Message is the elicitation message + Only used when Type is "elicitation" + type: string + onCancel: + description: |- + OnCancel defines the action to take when the user cancels/dismisses the elicitation + Only used when Type is "elicitation" + properties: + action: + default: abort + description: |- + Action defines the action to take when the user declines or cancels + - skip_remaining: Skip remaining steps in the workflow + - abort: Abort the entire workflow execution + - continue: Continue to the next step + enum: + - skip_remaining + - abort + - continue + type: string + type: object + onDecline: + description: |- + OnDecline defines the action to take when the user explicitly declines the elicitation + Only used when Type is "elicitation" + properties: + action: + default: abort + description: |- + Action defines the action to take when the user declines or cancels + - skip_remaining: Skip remaining steps in the workflow + - abort: Abort the entire workflow execution + - continue: Continue to the next step + enum: + - skip_remaining + - abort + - continue + type: string + type: object + 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 + maxRetries: + description: |- + MaxRetries is the maximum number of retries + Only used when Action is "retry" + type: integer + retryDelay: + description: |- + RetryDelay is the delay between retry attempts + Only used when Action is "retry" + pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m))+$ + type: string + type: object + schema: + description: Schema defines the expected response schema + for elicitation + type: object + x-kubernetes-preserve-unknown-fields: true + timeout: + description: Timeout is the maximum execution time for + this step + type: string + tool: + description: |- + Tool is the tool to call (format: "workload.tool_name") + Only used when Type is "tool" + type: string + type: + default: tool + description: Type is the step type (tool, elicitation, + etc.) + enum: + - tool + - elicitation + type: string + required: + - id + type: object + minItems: 1 + type: array + timeout: + default: 30m + description: Timeout is the maximum execution time for the composite + tool + type: string + required: + - description + - name + - steps + type: object + type: array + groupRef: + description: |- + GroupRef references an existing MCPGroup that defines backend workloads + The referenced MCPGroup must exist in the same namespace + properties: + name: + description: Name is the name of the MCPGroup resource in the + same namespace + type: string + required: + - name + type: object + incomingAuth: + description: |- + IncomingAuth configures authentication for clients connecting to the Virtual MCP server + Must be explicitly set - use "anonymous" type when no authentication is required + properties: + authzConfig: + description: |- + AuthzConfig defines authorization policy configuration + Reuses MCPServer authz patterns + properties: + configMap: + description: |- + ConfigMap references a ConfigMap containing authorization configuration + Only 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 + inline: + description: |- + Inline contains direct authorization configuration + Only used when Type is "inline" + properties: + entitiesJson: + default: '[]' + description: EntitiesJSON is a JSON string representing + Cedar entities + type: string + policies: + description: Policies is a list of Cedar policy strings + items: + type: string + minItems: 1 + type: array + required: + - policies + type: object + type: + default: configMap + description: Type is the type of authorization configuration + enum: + - configMap + - inline + type: string + required: + - type + type: object + oidcConfig: + description: |- + OIDCConfig defines OIDC authentication configuration + Reuses MCPServer OIDC patterns + properties: + configMap: + description: |- + ConfigMap references a ConfigMap containing OIDC configuration + Only used when Type is "configmap" + properties: + key: + default: oidc.json + description: Key is the key in the ConfigMap that contains + the OIDC configuration + type: string + name: + description: Name is the name of the ConfigMap + type: string + required: + - name + type: object + inline: + description: |- + Inline contains direct OIDC configuration + Only used when Type is "inline" + properties: + audience: + description: Audience is the expected audience for the + token + type: string + clientId: + description: ClientID is the OIDC client ID + type: string + clientSecret: + description: |- + ClientSecret is the client secret for introspection (optional) + Deprecated: Use ClientSecretRef instead for better security + type: string + clientSecretRef: + description: |- + ClientSecretRef is a reference to a Kubernetes Secret containing the client secret + If both ClientSecret and ClientSecretRef are provided, ClientSecretRef takes precedence + 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 + insecureAllowHTTP: + default: false + description: |- + InsecureAllowHTTP allows HTTP (non-HTTPS) OIDC issuers for development/testing + WARNING: This is insecure and should NEVER be used in production + Only enable for local development, testing, or trusted internal networks + 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 + Use with caution - only enable for trusted internal IDPs + type: boolean + jwksAuthTokenPath: + description: |- + JWKSAuthTokenPath is the path to file containing bearer token for JWKS/OIDC requests + The file must be mounted into the pod (e.g., via Secret volume) + 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 + Use with caution - only enable for trusted internal IDPs or testing + type: boolean + thvCABundlePath: + description: |- + ThvCABundlePath is the path to CA certificate bundle file for HTTPS requests + The file must be mounted into the pod (e.g., via ConfigMap or Secret volume) + type: string + required: + - issuer + type: object + kubernetes: + description: |- + Kubernetes configures OIDC for Kubernetes service account token validation + Only used when Type is "kubernetes" + properties: + audience: + default: toolhive + description: Audience is the expected audience for the + token + type: string + introspectionUrl: + description: |- + IntrospectionURL is the URL for token introspection endpoint + If 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 + If empty, OIDC discovery will be used to automatically determine the JWKS URL + type: string + namespace: + description: |- + Namespace is the namespace of the service account + If empty, uses the MCPServer's namespace + type: string + serviceAccount: + description: |- + ServiceAccount is the name of the service account to validate tokens for + If empty, uses the pod's service account + type: string + useClusterAuth: + description: |- + UseClusterAuth enables using the Kubernetes cluster's CA bundle and service account token + When true, uses /var/run/secrets/kubernetes.io/serviceaccount/ca.crt for TLS verification + and /var/run/secrets/kubernetes.io/serviceaccount/token for bearer token authentication + Defaults to true if not specified + type: boolean + type: object + resourceUrl: + description: |- + ResourceURL is the explicit resource URL for OAuth discovery endpoint (RFC 9728) + If not specified, defaults to the in-cluster Kubernetes service URL + type: string + type: + default: kubernetes + description: Type is the type of OIDC configuration + enum: + - kubernetes + - configMap + - inline + type: string + required: + - type + type: object + type: + description: |- + Type defines the authentication type: anonymous or oidc + When no authentication is required, explicitly set this to "anonymous" + enum: + - anonymous + - oidc + type: string + required: + - type + type: object + operational: + description: Operational defines operational settings like timeouts + and health checks + 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 + type: integer + timeout: + default: 60s + description: Timeout is the duration to wait before attempting + to close the circuit + type: string + type: object + healthCheckInterval: + default: 30s + description: HealthCheckInterval is the interval between health + checks + type: string + partialFailureMode: + default: fail + description: |- + PartialFailureMode defines behavior when some backends are unavailable + - fail: Fail entire request if any backend is unavailable + - best_effort: Continue with available backends + enum: + - fail + - best_effort + type: string + unhealthyThreshold: + default: 3 + description: UnhealthyThreshold is the number of consecutive + failures before marking unhealthy + type: integer + type: object + logLevel: + description: |- + LogLevel sets the logging level for the Virtual MCP server. + Set to "debug" to enable debug logging. When not set, defaults to info level. + enum: + - debug + type: string + timeouts: + description: Timeouts configures timeout settings + properties: + default: + default: 30s + description: Default is the default timeout for backend requests + type: string + perWorkload: + additionalProperties: + type: string + description: PerWorkload defines per-workload timeout overrides + type: object + type: object + type: object + outgoingAuth: + description: OutgoingAuth configures authentication from Virtual MCP + to backend MCPServers + properties: + backends: + additionalProperties: + description: BackendAuthConfig defines authentication configuration + for a backend MCPServer + properties: + externalAuthConfigRef: + description: |- + ExternalAuthConfigRef references an MCPExternalAuthConfig resource + Only used when Type is "external_auth_config_ref" + properties: + name: + description: Name is the name of the MCPExternalAuthConfig + resource + type: string + required: + - name + type: object + type: + description: Type defines the authentication type + enum: + - discovered + - external_auth_config_ref + type: string + required: + - type + type: object + description: |- + Backends defines per-backend authentication overrides + Works 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 + Only used when Type is "external_auth_config_ref" + properties: + name: + description: Name is the name of the MCPExternalAuthConfig + resource + type: string + required: + - name + type: object + type: + description: Type defines the authentication type + enum: + - discovered + - external_auth_config_ref + type: string + required: + - type + type: object + source: + default: discovered + description: |- + Source defines how backend authentication configurations are determined + - discovered: Automatically discover from backend's MCPServer.spec.externalAuthConfigRef + - inline: Explicit per-backend configuration in VirtualMCPServer + enum: + - discovered + - inline + type: string + type: object + podTemplateSpec: + description: |- + PodTemplateSpec defines the pod template to use for the Virtual MCP server + This allows for customizing the pod configuration beyond what is provided by the other fields. + Note that to modify the specific container the Virtual MCP server runs in, you must specify + the 'vmcp' container name in the PodTemplateSpec. + This field accepts a PodTemplateSpec object as JSON/YAML. + type: object + x-kubernetes-preserve-unknown-fields: true + serviceType: + default: ClusterIP + description: ServiceType specifies the Kubernetes service type for + the Virtual MCP server + enum: + - ClusterIP + - NodePort + - LoadBalancer + type: string + telemetry: + description: |- + Telemetry configures OpenTelemetry-based observability for the Virtual MCP server + including distributed tracing, OTLP metrics export, and Prometheus metrics endpoint + properties: + openTelemetry: + description: OpenTelemetry defines OpenTelemetry configuration + properties: + 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: + description: |- + Headers contains authentication headers for the OTLP endpoint + Specified as key=value pairs + items: + type: string + type: array + 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 + serviceName: + description: |- + ServiceName is the service name for telemetry + If not specified, defaults to the MCPServer name + type: string + 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) + type: string + type: object + type: object + prometheus: + description: Prometheus defines Prometheus-specific configuration + properties: + enabled: + default: false + description: Enabled controls whether Prometheus metrics endpoint + is exposed + type: boolean + type: object + type: object + required: + - groupRef + - incomingAuth + type: object + status: + description: VirtualMCPServerStatus defines the observed state of VirtualMCPServer + properties: + backendCount: + description: BackendCount is the number of discovered backends + 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. + This 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. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with 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. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This 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 + type: array + discoveredBackends: + description: DiscoveredBackends lists discovered backend configurations + from the MCPGroup + items: + description: DiscoveredBackend represents a discovered backend MCPServer + in the MCPGroup + 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 + lastHealthCheck: + description: LastHealthCheck is the timestamp of the last health + check + format: date-time + 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) + type: string + url: + description: URL is the URL of the backend MCPServer + type: string + required: + - name + type: object + type: array + 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 + phase: + default: Pending + description: Phase is the current phase of the VirtualMCPServer + enum: + - Pending + - Ready + - Degraded + - Failed + type: string + url: + description: URL is the URL where the Virtual MCP server can be accessed + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +{{- end }} diff --git a/deploy/charts/operator-crds/values.yaml b/deploy/charts/operator-crds/values.yaml index 804d4fc18..ad4b1d1a4 100644 --- a/deploy/charts/operator-crds/values.yaml +++ b/deploy/charts/operator-crds/values.yaml @@ -1 +1,13 @@ -# empty values file as we do not need any values for the CRDs at the moment +# -- CRD installation configuration +crds: + # -- Whether to add the "helm.sh/resource-policy: keep" annotation to CRDs + # When true, CRDs will not be deleted when the Helm release is uninstalled + keep: true + # -- Feature flags for CRD groups + install: + # -- Install Server CRDs (mcpservers, mcpremoteproxies, mcptoolconfigs, mcpgroups) + server: true + # -- Install Registry CRDs (mcpregistries) + registry: true + # -- Install VirtualMCP CRDs (virtualmcpservers, virtualmcpcompositetooldefinitions) + virtualMcp: true