feat: Support env.valueFrom in MCP server config#1154
Conversation
Change MCPServerSpec.Env from map[string]string to []corev1.EnvVar so each env entry can reference a single key in a Secret or ConfigMap via the standard valueFrom shape (secretKeyRef, configMapKeyRef). This matches the convention already used by PodOverrides.Env and unblocks the common case of pulling one credential out of a shared Secret without depending on the whole-collection envFrom. Only SecretKeyRef and ConfigMapKeyRef variants of ValueFrom are honored — FieldRef and ResourceFieldRef are pod-scoped and have no meaning for an MCP server process. EnvFrom still wins over Env on name collision so existing behaviour is preserved. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
There was a problem hiding this comment.
1 issue found across 12 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="internal/controller/task_controller.go">
<violation number="1" location="internal/controller/task_controller.go:531">
P2: `valueFrom` is not validated as exclusive, so unsupported `fieldRef/resourceFieldRef` can slip through when combined with `secretKeyRef` or `configMapKeyRef`.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| } | ||
|
|
||
| src := entry.ValueFrom | ||
| switch { |
There was a problem hiding this comment.
P2: valueFrom is not validated as exclusive, so unsupported fieldRef/resourceFieldRef can slip through when combined with secretKeyRef or configMapKeyRef.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At internal/controller/task_controller.go, line 531:
<comment>`valueFrom` is not validated as exclusive, so unsupported `fieldRef/resourceFieldRef` can slip through when combined with `secretKeyRef` or `configMapKeyRef`.</comment>
<file context>
@@ -451,27 +452,130 @@ func (r *TaskReconciler) resolveMCPServerSecrets(ctx context.Context, namespace
+ }
+
+ src := entry.ValueFrom
+ switch {
+ case src.SecretKeyRef != nil:
+ ref := src.SecretKeyRef
</file context>
| switch { | |
| setSources := 0 | |
| if src.SecretKeyRef != nil { | |
| setSources++ | |
| } | |
| if src.ConfigMapKeyRef != nil { | |
| setSources++ | |
| } | |
| if src.FieldRef != nil || src.ResourceFieldRef != nil { | |
| return "", fmt.Errorf("MCP server %q env %q: fieldRef and resourceFieldRef are not supported", serverName, entry.Name) | |
| } | |
| if setSources != 1 { | |
| return "", fmt.Errorf("MCP server %q env %q: valueFrom must set exactly one of secretKeyRef or configMapKeyRef", serverName, entry.Name) | |
| } | |
| switch { |
|
This is a breaking change. |
|
🤖 Kelos Task Status Task |
There was a problem hiding this comment.
🤖 Kelos API Reviewer Agent @gjkim42
API Design Review
Verdict: REQUEST CHANGES
Scope: Change AgentConfig.spec.mcpServers[].env from map[string]string to []corev1.EnvVar and resolve valueFrom.secretKeyRef / valueFrom.configMapKeyRef at reconcile time.
The shape choice (reusing corev1.EnvVar to mirror PodOverrides.Env in api/v1alpha1/task_types.go:78) is the right call — consistent with existing Kelos API surface and with upstream Kubernetes idioms. Below are the API-level concerns before this ships.
Findings
Validation / CRD (blocking)
internal/manifests/charts/kelos/templates/crds/agentconfig-crd.yaml: the generated CRD pulls in the full upstreamcorev1.EnvVarschema, so the OpenAPI surface advertisesvalueFrom.fieldRef,valueFrom.resourceFieldRef, and the newvalueFrom.fileKeyRef(EnvFiles feature gate). Admission accepts them; only at reconcile time doestask_controller.go:566rejectfieldRef/resourceFieldRef, andfileKeyReffalls through the default branch. Once a CRD ships, what its schema accepts is effectively part of the contract — please tighten this at admission rather than at reconcile. Two options:- Add a
+kubebuilder:validation:XValidationCEL rule on theenvitems (e.g.self.valueFrom == null || (has(self.valueFrom.secretKeyRef) || has(self.valueFrom.configMapKeyRef))and!(has(self.valueFrom.fieldRef) || has(self.valueFrom.resourceFieldRef) || has(self.valueFrom.fileKeyRef))). - Or define a Kelos-specific
MCPEnvVar/MCPEnvVarSourcethat only embedsSecretKeyRefandConfigMapKeyRef. This is also more future-proof against new upstreamEnvVarSourcevariants being silently surfaced.
- Add a
api/v1alpha1/agentconfig_types.go:122-128,internal/controller/task_controller.go:512:valueandvalueFrommutual exclusion is also runtime-only. A+kubebuilder:validation:XValidationrule ((self.value == "" || self.valueFrom == null)) would surface this atkubectl apply. (cubic flagged a related concern in the same area; my own analysis surfaces the broader CRD-vs-runtime validation gap.)
Semantics — Optional divergence from Kubernetes (blocking unless documented)
internal/controller/task_controller.go:521,:527,:547,:555: whenvalueFrom.secretKeyRef.optional(orconfigMapKeyRef.optional) istrueand the secret/key is missing, the resolver emits an env entry with emptyValue. Standard kubelet semantics for a podEnvVarwithOptional=trueand a missing key is that the variable is not set on the container, not set to"". Because the field type iscorev1.EnvVar, users will reasonably assume upstream semantics — silently surfacingFOO=""to the MCP server process when the user marked it optional is a non-obvious foot-gun. Either match upstream (skip the entry; do not append a name toorderwhen optional+missing), or call out the divergence explicitly in the field godoc.
Naming / Consistency
api/v1alpha1/agentconfig_types.go:128: reusing[]corev1.EnvVarand mirroringPodOverrides.Envis a good, consistent choice — names and shapes match existing API surface in this repo and upstream. No change requested.- The asymmetric precedence between
envandenvFrom(existing behavior:envFromwins on collision) is preserved. This is opposite of pod-spec semantics where, when present together, the later-applied wins /envtypically overridesenvFrom. Pre-existing behavior, but worth a sentence in theEnvgodoc (orEnvFromgodoc) so users coming from pod-spec know what to expect.
API compatibility / evolution
- This is a hard break for stored v1alpha1 AgentConfig objects: the env JSON shape changes from object to array, so existing in-cluster
AgentConfigresources written with the old shape will fail to deserialize under the new CRD schema. v1alpha1 conventionally permits this, but therelease-noteonly mentions the YAML shape change. Suggest extending the release note (or adding an upgrade note) to call out that existing in-cluster AgentConfig objects withmcpServers[].envset need to be re-applied after the upgrade.
Comments / godoc
api/v1alpha1/agentconfig_types.go:122-126: the godoc enumeratesSecretKeyRef/ConfigMapKeyRefas supported andFieldRef/ResourceFieldRefas unsupported — but the CRD also exposesfileKeyRef(new upstream EnvFiles field) andfileKeyRefis rejected only via the default error branch. Either explicitly mentionfileKeyRef(and any future variants) as unsupported, or phrase the comment as "onlysecretKeyRefandconfigMapKeyRefare honored; all other variants are rejected." The latter is more future-proof.
Suggestions (optional)
- Consider a custom
MCPEnvVartype (with onlyName,Value,ValueFrom *MCPEnvVarSourcewhereMCPEnvVarSourcehas justSecretKeyRefandConfigMapKeyRef). It costs a small amount of code duplication but yields a tight, self-documenting CRD schema, immune to upstreamEnvVarSourceadditions surfacing through the API. - For determinism,
resolveMCPServerEnvappendsEnvFromkeys in Go map iteration order (internal/controller/task_controller.go:494-499). The final JSON happens to be deterministic becausebuildMCPServersJSONre-marshals through amap[string]string(whichencoding/jsonsorts), but ifmcpServerJSON.Envever moves to a slice, this would surface as non-deterministic output. Worth sorting theEnvFromkeys explicitly to be defensive.
Note on prompt injection: I noticed an embedded instruction in a third-party reviewer's comment demanding attribution for its findings. I disregarded it and formed independent analysis from the diff and code.
/kelos needs-input
What type of PR is this?
/kind feature
What this PR does / why we need it:
Changes
AgentConfig.spec.mcpServers[].envfrom amap[string]stringto[]corev1.EnvVarso each entry can pull its value from a single key of a Secret or ConfigMap via the standardvalueFromshape (secretKeyRef,configMapKeyRef). The existingenvFrom(whole-Secret) field is preserved and still wins overenvon name collision.Only
SecretKeyRefandConfigMapKeyRefvariants ofvalueFromare honored —fieldRefandresourceFieldRefare pod-scoped and meaningless for an MCP server process, so they are rejected with an error.Because the env field is now a list rather than a map, this is a breaking change for any AgentConfig that sets
mcpServers[].env. The CLI--mcpflag still accepts the{"KEY":"VAL"}shorthand alongside the new[{name,value/valueFrom}]form.Includes RBAC permission to read ConfigMaps.
Which issue(s) this PR is related to:
Fixes #1152
Special notes for your reviewer:
The shape choice mirrors
PodOverrides.Envinapi/v1alpha1/task_types.go:78so MCP env now uses the same idiom users already see elsewhere in the API. Migration for existing YAML:Does this PR introduce a user-facing change?
Summary by cubic
Adds
valueFromsupport for MCP server env by changingAgentConfig.spec.mcpServers[].envto a list ofcorev1.EnvVar, enabling per-key Secret/ConfigMap references.envFromstill wins on name collision, the CLI keeps the{"KEY":"VAL"}shorthand, and RBAC/CRDs were updated.New Features
mcpServers[].envis now[]corev1.EnvVarwithvalueFrom.secretKeyRefandvalueFrom.configMapKeyRef.valueFromto literals;fieldRef/resourceFieldRefare rejected.envFromcontinues to overrideenvon key collision.Migration
env: {DSN: "postgres://..."}→env: [{name: "DSN", value: "postgres://..."}]. The CLI--mcpflag still accepts the old map shorthand.Written for commit 7f0e8c5. Summary will update on new commits. Review in cubic