Skip to content

feat: add envFrom secret injection for stdio MCP servers#114

Open
alexeykazakov wants to merge 1 commit intocodeready-toolchain:masterfrom
alexeykazakov:feat/mcp-envfrom-secret-injection
Open

feat: add envFrom secret injection for stdio MCP servers#114
alexeykazakov wants to merge 1 commit intocodeready-toolchain:masterfrom
alexeykazakov:feat/mcp-envfrom-secret-injection

Conversation

@alexeykazakov
Copy link
Copy Markdown
Contributor

@alexeykazakov alexeykazakov commented May 9, 2026

  • Add McpEnvFromSecret type and EnvFrom field to McpServerSpec
  • Validate referenced Secrets exist with specified keys before reconciling
  • Inject secretKeyRef env vars onto gateway container for envFrom entries
  • Stamp MCP secret ResourceVersions as annotations for rollout on change
  • Set McpServersConfigured=False on validation failure with recovery path
  • Include envFrom names as placeholder values in operator.json env map

Summary by CodeRabbit

  • New Features
    • Added support for secret-backed environment variables for MCP servers, enabling gateway containers to securely access sensitive data stored in Kubernetes Secrets.
    • Implemented automatic rollout triggering when referenced MCP secret data changes to ensure configurations stay up-to-date.
    • Added validation to ensure all referenced secrets exist and contain the required keys.

- Add McpEnvFromSecret type and EnvFrom field to McpServerSpec
- Validate referenced Secrets exist with specified keys before reconciling
- Inject secretKeyRef env vars onto gateway container for envFrom entries
- Stamp MCP secret ResourceVersions as annotations for rollout on change
- Set McpServersConfigured=False on validation failure with recovery path
- Include envFrom names as placeholder values in operator.json env map

Signed-off-by: Alexey Kazakov <alkazako@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 9, 2026

Walkthrough

This PR adds support for MCP (Model Context Protocol) server secret-backed environment variables. It introduces new API types (McpEnvFromSecret, EnvFrom field on McpServerSpec), controller logic to validate and mount referenced Kubernetes Secrets into the gateway container, and annotation stamping to trigger rollouts when secrets change.

Changes

MCP Secret Environment Variable Support

Layer / File(s) Summary
Type Definitions & Schema
api/v1alpha1/claw_types.go, api/v1alpha1/zz_generated.deepcopy.go, config/crd/bases/claw.sandbox.redhat.com_claws.yaml
Added McpEnvFromSecret type with Name and SecretRef fields. Extended McpServerSpec with EnvFrom []McpEnvFromSecret. Generated deepcopy methods for the new type. Updated CRD schema to define spec.mcpServers[*].envFrom array with required name and secretRef (containing name, key, and optional role).
Validation Logic
internal/controller/claw_mcp.go
Introduced validateMcpServerSecrets to verify that all referenced secrets exist in the namespace and contain the specified keys. Errors are aggregated and returned together. Updated buildMcpServerConfig to construct env vars from both spec.Env and spec.EnvFrom entries.
Deployment Configuration
internal/controller/claw_deployment.go
Added configureGatewayForMcpServers to inject secret-backed environment variables into the gateway container using valueFrom.secretKeyRef. Added stampMcpSecretVersionAnnotation to fetch referenced secrets and stamp gateway pod template annotations with secret resource versions, triggering rollouts on secret changes.
Controller Integration
internal/controller/claw_resource_controller.go
Integrated validation into the Reconcile loop immediately after credential resolution; validation failure sets McpServersConfigured and Ready conditions to False. Added calls to configure the gateway and stamp secret version annotations during deployment configuration.
Tests & Documentation
internal/controller/claw_deployment_test.go, internal/controller/claw_mcp_test.go, docs/proposals/mcp-support-design.md
Added test suite for gateway MCP configuration covering multiple servers, multiple envFrom entries, error handling, and env var preservation. Extended MCP config and integration tests to validate envFrom behavior, placeholder env vars, secret validation failures, and annotation stamping. Updated design documentation to mark Phase 2 (stdio MCP secret injection) as complete.

Sequence Diagram

sequenceDiagram
    participant Controller as Controller<br/>(Reconcile)
    participant API as Kubernetes API<br/>(Secrets)
    participant Deployment as Gateway<br/>Deployment
    participant ConfigMap as ConfigMap<br/>(MCP Config)

    Controller->>Controller: Receive reconciliation request
    Controller->>Controller: Validate MCP envFrom<br/>secret references exist
    alt Validation Fails
        Controller->>Controller: Set McpServersConfigured=False<br/>Update status
        Controller-->>Deployment: (no changes)
    else Validation Succeeds
        Controller->>API: Fetch referenced secrets<br/>for version info
        API-->>Controller: Return secrets + ResourceVersions
        Controller->>Deployment: Configure gateway container<br/>with valueFrom.secretKeyRef<br/>env vars
        Controller->>Deployment: Stamp pod annotations<br/>with secret versions<br/>(triggers rollout)
        Controller->>ConfigMap: Inject envFrom placeholders<br/>into MCP config
        Controller->>Controller: Set McpServersConfigured=True
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

feature, test, documentation

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.55% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add envFrom secret injection for stdio MCP servers' directly and accurately describes the main change: introducing envFrom support for secret injection on stdio MCP servers.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot added documentation Improvements or additions to documentation feature New feature or request test Work that adds, fixes, or maintains automated tests or coverage (unit, integration, e2e, flakiness) labels May 9, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@api/v1alpha1/claw_types.go`:
- Around line 247-251: Add API-level validation to reject EnvFrom when the
server is not a stdio MCP: implement/extend the CRD admission validation
(ValidateCreate and ValidateUpdate on the MCP type) to check the EnvFrom
[]McpEnvFromSecret field and return an error if it is non-empty while the server
is configured with a URL (i.e., Url is set) or otherwise not a stdio server; use
the EnvFrom and Url field names in the validation logic and include a clear
error message instructing users that envFrom is only allowed for stdio MCP
servers.

In `@internal/controller/claw_deployment.go`:
- Around line 417-467: The env injection is non-deterministic and can create
duplicate/overriding entries; to fix, build a deterministic, de-duplicated list
from instance.Spec.McpServers -> envFromEntries by sorting entries (e.g., by
Name then SecretRef.Name then SecretRef.Key) and collapsing duplicates by env
name, then examine the existing container envs (use unstructured.NestedSlice on
the container found for ClawGatewayContainerName) and for each desired env: if
an env with the same name exists and its secretKeyRef matches, skip (no-op); if
it exists but differs, replace it deterministically; otherwise append; finally
write the merged env slice back into the container so updates are stable (use
the same unstructured helpers you already use such as
unstructured.NestedSlice/NestedString and update the obj for the gateway
returned by getClawDeploymentName and filtered by DeploymentKind).
- Around line 500-521: The current annotation key built as key := serverName +
"-" + ef.Name can produce invalid Kubernetes annotation keys for unconstrained
McpServers keys or McpEnvFromSecret.Name; replace this concatenation with a
validated, deterministic identifier (e.g., compute a short hash or sanitized
slug of serverName and ef.Name) and use that identifier when populating versions
and when writing annotations (the code that sets
annotations[clawv1alpha1.AnnotationPrefixMcpSecretVersion+key+clawv1alpha1.AnnotationSuffixMcpSecretVersion]
= rv). Ensure the hashing/sanitization always produces only allowed characters,
keeps the length below 63 characters, and remains stable so the same inputs map
to the same annotation key.

In `@internal/controller/claw_resource_controller.go`:
- Around line 461-464: The reconcile path won't be triggered for MCP-only
envFrom secret changes because findClawsReferencingSecret only inspects
credential secret refs; update the secret-to-Claw mapping so MCP envFrom secrets
are included: extend findClawsReferencingSecret (or the controller's Secret
watch mapper) to also search MCP-related EnvFromSource entries (and any MCP
field that references a Secret) for matches and return the associated Claw
objects so that stampMcpSecretVersionAnnotation is invoked on envFrom secret
updates.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: 95b24f93-bdf3-4464-8e4f-e7ad9b9e8b12

📥 Commits

Reviewing files that changed from the base of the PR and between ff3d59d and f82554a.

📒 Files selected for processing (9)
  • api/v1alpha1/claw_types.go
  • api/v1alpha1/zz_generated.deepcopy.go
  • config/crd/bases/claw.sandbox.redhat.com_claws.yaml
  • docs/proposals/mcp-support-design.md
  • internal/controller/claw_deployment.go
  • internal/controller/claw_deployment_test.go
  • internal/controller/claw_mcp.go
  • internal/controller/claw_mcp_test.go
  • internal/controller/claw_resource_controller.go
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Unit Tests
  • GitHub Check: E2E Tests
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

-Focus on major issues impacting performance, readability, maintainability and security. Avoid nitpicks and avoid verbosity.

Files:

  • api/v1alpha1/zz_generated.deepcopy.go
  • config/crd/bases/claw.sandbox.redhat.com_claws.yaml
  • internal/controller/claw_deployment.go
  • api/v1alpha1/claw_types.go
  • internal/controller/claw_resource_controller.go
  • internal/controller/claw_mcp_test.go
  • internal/controller/claw_deployment_test.go
  • docs/proposals/mcp-support-design.md
  • internal/controller/claw_mcp.go
🪛 LanguageTool
docs/proposals/mcp-support-design.md

[grammar] ~254-~254: Ensure spelling is correct
Context: ... and claw_proxy_test.go ### Phase 2: Stdio MCP secret injection (tier 3 — escape h...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🔇 Additional comments (6)
internal/controller/claw_mcp_test.go (6)

26-31: LGTM!

The new imports are appropriately added to support the integration tests for deployment verification and reconcile request construction.


244-330: LGTM!

Well-structured unit tests for buildMcpServerConfig covering the key scenarios: envFrom merging with plain env, envFrom-only, neither env nor envFrom, HTTP server exclusion, and multiple envFrom entries.


332-359: LGTM!

Good integration test verifying that injectMcpServersIntoConfigMap correctly propagates both plain env values and envFrom placeholder values into the MCP server configuration.


551-635: LGTM!

Comprehensive integration test that validates the full reconciliation flow: ConfigMap placeholder injection, gateway deployment secretKeyRef env var mounting, and condition status verification.


637-733: LGTM!

Well-structured failure path tests that verify both the reconcile error and condition state when envFrom secrets or keys are missing. The message assertions ensure user-facing error details are informative.


735-897: LGTM!

Excellent test coverage including:

  • Secret version annotation stamping for rollout triggers
  • Backward compatibility for MCP servers without envFrom
  • Recovery path from validation failure after creating missing secret
  • CEL validation acceptance of envFrom configuration

Comment on lines +247 to +251
// EnvFrom are secret-backed environment variables mounted on the gateway
// container and inherited by the stdio server subprocess (tier 3).
// Use only when the proxy-placeholder pattern (tier 2) is not viable.
// +optional
EnvFrom []McpEnvFromSecret `json:"envFrom,omitempty"`
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Restrict envFrom to stdio MCP servers at the API level.

envFrom is documented as stdio-only, but the schema currently allows it when url is set. That permits unnecessary secret exposure on gateway for HTTP servers.

🔧 Suggested validation
 // +kubebuilder:validation:XValidation:rule="has(self.command) || has(self.url)",message="either command (stdio) or url (HTTP) must be set"
 // +kubebuilder:validation:XValidation:rule="!has(self.command) || !has(self.url)",message="command and url are mutually exclusive"
+// +kubebuilder:validation:XValidation:rule="!has(self.url) || !has(self.envFrom)",message="envFrom is only valid for stdio MCP servers"
 type McpServerSpec struct {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/v1alpha1/claw_types.go` around lines 247 - 251, Add API-level validation
to reject EnvFrom when the server is not a stdio MCP: implement/extend the CRD
admission validation (ValidateCreate and ValidateUpdate on the MCP type) to
check the EnvFrom []McpEnvFromSecret field and return an error if it is
non-empty while the server is configured with a URL (i.e., Url is set) or
otherwise not a stdio server; use the EnvFrom and Url field names in the
validation logic and include a clear error message instructing users that
envFrom is only allowed for stdio MCP servers.

Comment on lines +417 to +467
var envFromEntries []clawv1alpha1.McpEnvFromSecret
for _, spec := range instance.Spec.McpServers {
envFromEntries = append(envFromEntries, spec.EnvFrom...)
}
if len(envFromEntries) == 0 {
return nil
}

gatewayName := getClawDeploymentName(instance.Name)
for _, obj := range objects {
if obj.GetKind() != DeploymentKind || obj.GetName() != gatewayName {
continue
}

containers, found, err := unstructured.NestedSlice(obj.Object, "spec", "template", "spec", "containers")
if err != nil {
return fmt.Errorf("failed to get containers from claw deployment: %w", err)
}
if !found {
return fmt.Errorf("containers field not found in claw deployment")
}

containerIdx := -1
var container map[string]any
for i, c := range containers {
cm, ok := c.(map[string]any)
if !ok {
continue
}
if name, _, _ := unstructured.NestedString(cm, "name"); name == ClawGatewayContainerName {
containerIdx = i
container = cm
break
}
}
if containerIdx < 0 {
return fmt.Errorf("container %q not found in claw deployment", ClawGatewayContainerName)
}

envVars, _, _ := unstructured.NestedSlice(container, "env")
for _, ef := range envFromEntries {
envVars = append(envVars, map[string]any{
"name": ef.Name,
"valueFrom": map[string]any{
"secretKeyRef": map[string]any{
"name": ef.SecretRef.Name,
"key": ef.SecretRef.Key,
},
},
})
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Stabilize and validate gateway env injection to prevent collisions and churn.

Line 418 iterates over a map, so injected env var order is non-deterministic. Also, env names are appended without duplicate checks, which can override existing container envs or conflict across MCP servers.

🔧 Suggested hardening
 func configureGatewayForMcpServers(objects []*unstructured.Unstructured, instance *clawv1alpha1.Claw) error {
-	var envFromEntries []clawv1alpha1.McpEnvFromSecret
-	for _, spec := range instance.Spec.McpServers {
-		envFromEntries = append(envFromEntries, spec.EnvFrom...)
-	}
+	type envBinding struct {
+		name      string
+		secret    string
+		secretKey string
+	}
+	var envFromEntries []envBinding
+	serverNames := make([]string, 0, len(instance.Spec.McpServers))
+	for name := range instance.Spec.McpServers {
+		serverNames = append(serverNames, name)
+	}
+	sort.Strings(serverNames)
+	for _, serverName := range serverNames {
+		spec := instance.Spec.McpServers[serverName]
+		for _, ef := range spec.EnvFrom {
+			envFromEntries = append(envFromEntries, envBinding{
+				name: ef.Name, secret: ef.SecretRef.Name, secretKey: ef.SecretRef.Key,
+			})
+		}
+	}
 	if len(envFromEntries) == 0 {
 		return nil
 	}
@@
-		envVars, _, _ := unstructured.NestedSlice(container, "env")
-		for _, ef := range envFromEntries {
+		envVars, _, _ := unstructured.NestedSlice(container, "env")
+		seen := map[string]struct{}{}
+		for _, e := range envVars {
+			if em, ok := e.(map[string]any); ok {
+				if n, ok := em["name"].(string); ok && n != "" {
+					seen[n] = struct{}{}
+				}
+			}
+		}
+		for _, ef := range envFromEntries {
+			if _, exists := seen[ef.name]; exists {
+				return fmt.Errorf("duplicate gateway env var %q from MCP envFrom", ef.name)
+			}
+			seen[ef.name] = struct{}{}
 			envVars = append(envVars, map[string]any{
-				"name": ef.Name,
+				"name": ef.name,
 				"valueFrom": map[string]any{
 					"secretKeyRef": map[string]any{
-						"name": ef.SecretRef.Name,
-						"key":  ef.SecretRef.Key,
+						"name": ef.secret,
+						"key":  ef.secretKey,
 					},
 				},
 			})
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/controller/claw_deployment.go` around lines 417 - 467, The env
injection is non-deterministic and can create duplicate/overriding entries; to
fix, build a deterministic, de-duplicated list from instance.Spec.McpServers ->
envFromEntries by sorting entries (e.g., by Name then SecretRef.Name then
SecretRef.Key) and collapsing duplicates by env name, then examine the existing
container envs (use unstructured.NestedSlice on the container found for
ClawGatewayContainerName) and for each desired env: if an env with the same name
exists and its secretKeyRef matches, skip (no-op); if it exists but differs,
replace it deterministically; otherwise append; finally write the merged env
slice back into the container so updates are stable (use the same unstructured
helpers you already use such as unstructured.NestedSlice/NestedString and update
the obj for the gateway returned by getClawDeploymentName and filtered by
DeploymentKind).

Comment on lines +500 to +521
key := serverName + "-" + ef.Name
versions[key] = secret.ResourceVersion
}
}

if len(versions) == 0 {
return nil
}

gatewayName := getClawDeploymentName(instance.Name)
for _, obj := range objects {
if obj.GetKind() != DeploymentKind || obj.GetName() != gatewayName {
continue
}

annotations, _, _ := unstructured.NestedStringMap(obj.Object, "spec", "template", "metadata", "annotations")
if annotations == nil {
annotations = make(map[string]string)
}
for key, rv := range versions {
annotations[clawv1alpha1.AnnotationPrefixMcpSecretVersion+key+clawv1alpha1.AnnotationSuffixMcpSecretVersion] = rv
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "AnnotationPrefixMcpSecretVersion|AnnotationSuffixMcpSecretVersion|serverName \\+ \"-\" \\+ ef.Name" internal/controller/claw_deployment.go api/v1alpha1/claw_types.go -C2
rg -n "type McpServerSpec|EnvFrom \\[]McpEnvFromSecret|type McpEnvFromSecret|Name string `json:\"name\"`" api/v1alpha1/claw_types.go -C2

Repository: codeready-toolchain/claw-operator

Length of output: 2711


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find where serverName comes from in the function context
rg -B 30 "key := serverName" internal/controller/claw_deployment.go | head -50

# Check if serverName has any validation constraints
rg -n "serverName" internal/controller/claw_deployment.go | head -20

Repository: codeready-toolchain/claw-operator

Length of output: 1480


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check the CRD definition for MCP servers - look for serverName/name field constraints
rg -A 10 "type McpServer |name.*string|Name.*string" api/v1alpha1/claw_types.go | grep -A 10 "McpServer"

Repository: codeready-toolchain/claw-operator

Length of output: 59


🏁 Script executed (no clone):

# Verify Kubernetes annotation key constraints
curl -s "https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/" 2>/dev/null | grep -i "annotation.*key\|valid.*key\|key.*rule" | head -5 || echo "Will verify from k8s docs knowledge"

Length of output: 1966


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find McpServers field definition in the CRD
rg -n "McpServers" api/v1alpha1/claw_types.go -B 3 -A 3

# Also search for field definitions to understand constraints
rg -n "type Claw struct" api/v1alpha1/claw_types.go -A 30

Repository: codeready-toolchain/claw-operator

Length of output: 1373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Look for validation rules on name-related fields
rg -n "kubebuilder:validation" api/v1alpha1/claw_types.go | grep -i "name\|max\|pattern\|min" | head -20

Repository: codeready-toolchain/claw-operator

Length of output: 558


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Get the full McpEnvFromSecret definition with all validation rules
rg -n "type McpEnvFromSecret" api/v1alpha1/claw_types.go -A 20

# Also check if there's pattern validation on any name fields
rg -n "Pattern" api/v1alpha1/claw_types.go

Repository: codeready-toolchain/claw-operator

Length of output: 900


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check if there are validation rules elsewhere on serverName (CRD patterns, webhook validation, etc)
rg -n "McpServers|serverName" api/v1alpha1/claw_types.go | head -10

# Check if there's any validation/transformation of serverName in the controller
rg -n "serverName" internal/controller/ -B 3 -A 3

Repository: codeready-toolchain/claw-operator

Length of output: 3291


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check the actual line 282 and surrounding context for any kubebuilder validation on McpServers
sed -n '278,290p' api/v1alpha1/claw_types.go

Repository: codeready-toolchain/claw-operator

Length of output: 559


MCP secret-version annotation keys can become invalid for legal CR values.

The annotation key is constructed from serverName + "-" + ef.Name without validation. Since McpServers map keys and McpEnvFromSecret.Name field have no constraints preventing uppercase letters, underscores, special characters, or excessive length, the resulting annotation key can violate Kubernetes naming rules (must start/end with [a-z0-9A-Z], allow only [-_.] between, max 63 characters) and cause deployment failures.

Consider hashing the inputs to ensure keys remain valid regardless of user-supplied values:

🔧 Suggested approach
+func mcpSecretAnnotationToken(serverName, envName string) string {
+	raw := serverName + "-" + envName
+	sum := sha256.Sum256([]byte(raw))
+	return fmt.Sprintf("%x", sum[:8])
+}
@@
-			key := serverName + "-" + ef.Name
+			key := mcpSecretAnnotationToken(serverName, ef.Name)
 			versions[key] = secret.ResourceVersion
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
key := serverName + "-" + ef.Name
versions[key] = secret.ResourceVersion
}
}
if len(versions) == 0 {
return nil
}
gatewayName := getClawDeploymentName(instance.Name)
for _, obj := range objects {
if obj.GetKind() != DeploymentKind || obj.GetName() != gatewayName {
continue
}
annotations, _, _ := unstructured.NestedStringMap(obj.Object, "spec", "template", "metadata", "annotations")
if annotations == nil {
annotations = make(map[string]string)
}
for key, rv := range versions {
annotations[clawv1alpha1.AnnotationPrefixMcpSecretVersion+key+clawv1alpha1.AnnotationSuffixMcpSecretVersion] = rv
}
key := mcpSecretAnnotationToken(serverName, ef.Name)
versions[key] = secret.ResourceVersion
}
}
if len(versions) == 0 {
return nil
}
gatewayName := getClawDeploymentName(instance.Name)
for _, obj := range objects {
if obj.GetKind() != DeploymentKind || obj.GetName() != gatewayName {
continue
}
annotations, _, _ := unstructured.NestedStringMap(obj.Object, "spec", "template", "metadata", "annotations")
if annotations == nil {
annotations = make(map[string]string)
}
for key, rv := range versions {
annotations[clawv1alpha1.AnnotationPrefixMcpSecretVersion+key+clawv1alpha1.AnnotationSuffixMcpSecretVersion] = rv
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/controller/claw_deployment.go` around lines 500 - 521, The current
annotation key built as key := serverName + "-" + ef.Name can produce invalid
Kubernetes annotation keys for unconstrained McpServers keys or
McpEnvFromSecret.Name; replace this concatenation with a validated,
deterministic identifier (e.g., compute a short hash or sanitized slug of
serverName and ef.Name) and use that identifier when populating versions and
when writing annotations (the code that sets
annotations[clawv1alpha1.AnnotationPrefixMcpSecretVersion+key+clawv1alpha1.AnnotationSuffixMcpSecretVersion]
= rv). Ensure the hashing/sanitization always produces only allowed characters,
keeps the length below 63 characters, and remains stable so the same inputs map
to the same annotation key.

Comment on lines +461 to +464
// Stamp MCP envFrom Secret versions on gateway deployment for rollout
if err := r.stampMcpSecretVersionAnnotation(ctx, objects, instance); err != nil {
return ctrl.Result{}, fmt.Errorf("failed to stamp MCP secret version annotations: %w", err)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

MCP secret updates won’t reliably trigger this new rollout path.

This stamping logic is correct, but reconcile won’t run for many MCP secret changes because findClawsReferencingSecret currently maps only credential secret references. MCP-only envFrom secrets can change without enqueueing the Claw.

🔧 Suggested mapper update
 func (r *ClawResourceReconciler) findClawsReferencingSecret(ctx context.Context, obj client.Object) []reconcile.Request {
@@
 	for _, instance := range openClawList.Items {
+		matches := false
 		for _, cred := range instance.Spec.Credentials {
 			if referencesSecret(cred, secret.Name) {
-				requests = append(requests, reconcile.Request{
-					NamespacedName: types.NamespacedName{
-						Name:      instance.Name,
-						Namespace: instance.Namespace,
-					},
-				})
-				break
+				matches = true
+				break
+			}
+		}
+		if !matches {
+			for _, mcp := range instance.Spec.McpServers {
+				for _, ef := range mcp.EnvFrom {
+					if ef.SecretRef.Name == secret.Name {
+						matches = true
+						break
+					}
+				}
+				if matches {
+					break
+				}
 			}
 		}
+		if matches {
+			requests = append(requests, reconcile.Request{
+				NamespacedName: types.NamespacedName{
+					Name:      instance.Name,
+					Namespace: instance.Namespace,
+				},
+			})
+		}
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/controller/claw_resource_controller.go` around lines 461 - 464, The
reconcile path won't be triggered for MCP-only envFrom secret changes because
findClawsReferencingSecret only inspects credential secret refs; update the
secret-to-Claw mapping so MCP envFrom secrets are included: extend
findClawsReferencingSecret (or the controller's Secret watch mapper) to also
search MCP-related EnvFromSource entries (and any MCP field that references a
Secret) for matches and return the associated Claw objects so that
stampMcpSecretVersionAnnotation is invoked on envFrom secret updates.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation feature New feature or request test Work that adds, fixes, or maintains automated tests or coverage (unit, integration, e2e, flakiness)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant