fix(sandbox-controller): emit canonical SANDBOX_* env vars for MCP plugin (Refs #1986)

TBD-P4 B4 — env-var name drift between the sandbox-controller and the
MCP plugin silently degraded every MCP tool family to "not configured"
at runtime. The controller emitted bare `ORG_ID` and `SOVEREIGN_FQDN`
on every rendered MCP Deployment while the MCP binary
(products/sandbox/mcp-server/internal/tools/env.go) reads the
namespaced canonical `SANDBOX_ORG_ID` / `SANDBOX_SOVEREIGN_FQDN`. Per
agent a99ea3aa's investigation, six additional env-var families the
MCP requires were never wired at all.

Surgical alignment across renderer + chart + controller wiring:

1. core/controllers/sandbox/internal/gitops/manifests.go — MCP
   Deployment template renamed the bare names AND grew env entries
   for the canonical set the MCP plugin reads:

   Rename (MCP Deployment only; pty-server StatefulSet keeps the bare
   names since they are inherited into the user's agent shell — that
   is a distinct contract):
     ORG_ID         -> SANDBOX_ORG_ID            (tool family: all)
     SOVEREIGN_FQDN -> SANDBOX_SOVEREIGN_FQDN    (tool family: all)

   Added (the MCP plugin was reading them; controller wasn't emitting):
     SANDBOX_ID                    -> identifies the Sandbox CR
     SANDBOX_NAMESPACE             -> rendered ns sandbox-<owner-uid>
     SANDBOX_TENANT_ID             -> scopes marketplace/byod handler
     SANDBOX_GITEA_BASE_URL        -> sandbox.deploy / gitea tool family
     SANDBOX_GITEA_TOKEN (secret)  -> ditto, via secretKeyRef optional
     SANDBOX_DOMAIN_API_URL        -> marketplace tool family
     SANDBOX_MARKETPLACE_API_URL   -> marketplace tool family
     SANDBOX_STORAGE_S3_ENDPOINT   -> sandbox.storage tool family
     SANDBOX_STORAGE_S3_REGION     -> ditto
     SANDBOX_STORAGE_S3_USE_TLS    -> ditto
     SANDBOX_STORAGE_S3_ACCESS_KEY -> ditto, via secretKeyRef optional
     SANDBOX_STORAGE_S3_SECRET_KEY -> ditto, via secretKeyRef optional
     KEYCLOAK_ADMIN_URL            -> sandbox.auth tool family
     KEYCLOAK_PARENT_REALM         -> ditto
     KEYCLOAK_ADMIN_TOKEN (secret) -> ditto, via secretKeyRef optional

2. platform/sandbox/chart — bp-sandbox HR surfaces the new wiring as
   chart-level values (mcp.giteaBaseURL, mcp.domainAPIURL,
   mcp.storage.*, mcp.keycloak.*) defaulting to the in-cluster Service
   DNS of a stock Sovereign install. Per-Sovereign overlays may
   override any value. Secrets are NEVER written from this chart —
   name+key references only with `optional: true` so a fresh-prov
   Sovereign with a credential source in flight does NOT crash the
   per-Sandbox MCP Pod; the affected tool family surfaces a clean
   "not configured" error at call time (matches the MCP plugin's
   existing per-tool guard pattern).

3. Chart.yaml + bootstrap-kit pin (19a-bp-sandbox.yaml) bumped to
   0.2.0 so the per-Sovereign overlay picks up the new env surface
   on the next reconcile.

4. sandbox_controller_test.go — extended deployment-mcp.yaml assertion
   block to assert the canonical SANDBOX_* env-var set + value
   plumbing AND added a negative assertion that the bare `ORG_ID` /
   `SOVEREIGN_FQDN` names MUST NOT appear on the MCP Deployment
   (they remain on the pty-server StatefulSet, distinct contract).
   Regression test against future re-introduction of the drift.

Validation:
 - go test ./sandbox/... — all green (controller / gitops / idlescaler
   / newapi / sandboxapi).
 - helm template platform/sandbox/chart --set enabled=true ... — clean
   render, 16 SANDBOX_MCP_* env vars emitted on the controller
   Deployment.

Hard rules honoured:
 - READ-ONLY against existing cluster (no kubectl writes).
 - No Secret writes — name+key references only, all `optional: true`.
 - emrah.baysal mailbox + Stalwart admin untouched.
 - Principle #12 fresh clone validation.

Refs #1986

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
hatiyildiz 2026-05-19 22:00:57 +02:00
parent 6fc50b2719
commit 88453dc4c2
8 changed files with 340 additions and 5 deletions

View File

@ -68,7 +68,7 @@ spec:
chart:
spec:
chart: sandbox
version: 0.1.0
version: 0.2.0
sourceRef:
kind: HelmRepository
name: bp-sandbox

View File

@ -98,6 +98,28 @@ func main() {
primaryRegion := envOr("SOVEREIGN_PRIMARY_REGION", "")
replicaRegion := envOr("SOVEREIGN_REPLICA_REGION", "")
// TBD-P4 B4 — canonical SANDBOX_* env wiring for the MCP plugin
// (products/sandbox/mcp-server/internal/tools/env.go). All have
// in-cluster defaults; per-Sovereign overlays may override via
// bp-sandbox HR values. Empty leaves the MCP's per-tool guard to
// surface "not configured" at call time rather than crashing the
// controller at startup.
mcpGiteaBaseURL := envOr("SANDBOX_MCP_GITEA_BASE_URL", giteaURL)
mcpGiteaTokenSecretName := envOr("SANDBOX_MCP_GITEA_TOKEN_SECRET_NAME", "catalyst-gitea-token")
mcpGiteaTokenSecretKey := envOr("SANDBOX_MCP_GITEA_TOKEN_SECRET_KEY", "token")
mcpDomainAPIURL := envOr("SANDBOX_MCP_DOMAIN_API_URL", "http://domain.sme.svc.cluster.local:8086")
mcpMarketplaceAPIURL := envOr("SANDBOX_MCP_MARKETPLACE_API_URL", "http://marketplace-api.marketplace.svc.cluster.local:8082")
mcpStorageS3Endpoint := envOr("SANDBOX_MCP_STORAGE_S3_ENDPOINT", "http://seaweedfs.storage.svc.cluster.local:8333")
mcpStorageS3Region := envOr("SANDBOX_MCP_STORAGE_S3_REGION", "us-east-1")
mcpStorageS3UseTLS := envOr("SANDBOX_MCP_STORAGE_S3_USE_TLS", "false")
mcpStorageS3CredsSecret := envOr("SANDBOX_MCP_STORAGE_S3_CREDS_SECRET_NAME", "")
mcpStorageS3AccessKeyKey := envOr("SANDBOX_MCP_STORAGE_S3_ACCESS_KEY_KEY", "AWS_ACCESS_KEY_ID")
mcpStorageS3SecretKeyKey := envOr("SANDBOX_MCP_STORAGE_S3_SECRET_KEY_KEY", "AWS_SECRET_ACCESS_KEY")
mcpKeycloakAdminURL := envOr("SANDBOX_MCP_KEYCLOAK_ADMIN_URL", "http://keycloak.keycloak.svc.cluster.local:8080")
mcpKeycloakParentRealm := envOr("SANDBOX_MCP_KEYCLOAK_PARENT_REALM", "master")
mcpKeycloakAdminTokenSecret := envOr("SANDBOX_MCP_KEYCLOAK_ADMIN_TOKEN_SECRET_NAME", "")
mcpKeycloakAdminTokenSecretKey := envOr("SANDBOX_MCP_KEYCLOAK_ADMIN_TOKEN_SECRET_KEY", "token")
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
Metrics: metricsserver.Options{BindAddress: metricsAddr},
@ -153,6 +175,22 @@ func main() {
EnableHotStandby: enableHotStandby,
PrimaryRegion: primaryRegion,
ReplicaRegion: replicaRegion,
// TBD-P4 B4 — canonical SANDBOX_* env-var wiring for MCP plugin.
GiteaBaseURL: mcpGiteaBaseURL,
GiteaTokenSecretName: mcpGiteaTokenSecretName,
GiteaTokenSecretKey: mcpGiteaTokenSecretKey,
DomainAPIURL: mcpDomainAPIURL,
MarketplaceAPIURL: mcpMarketplaceAPIURL,
StorageS3Endpoint: mcpStorageS3Endpoint,
StorageS3Region: mcpStorageS3Region,
StorageS3UseTLS: mcpStorageS3UseTLS,
StorageS3CredsSecretName: mcpStorageS3CredsSecret,
StorageS3AccessKeyKey: mcpStorageS3AccessKeyKey,
StorageS3SecretKeyKey: mcpStorageS3SecretKeyKey,
KeycloakAdminURL: mcpKeycloakAdminURL,
KeycloakParentRealm: mcpKeycloakParentRealm,
KeycloakAdminTokenSecret: mcpKeycloakAdminTokenSecret,
KeycloakAdminTokenSecretKey: mcpKeycloakAdminTokenSecretKey,
}
if err := r.SetupWithManager(mgr); err != nil {
log.Error(err, "setup reconciler")

View File

@ -91,6 +91,31 @@ type Reconciler struct {
PrimaryRegion string
ReplicaRegion string
// TBD-P4 B4 — canonical SANDBOX_* env wiring the controller threads
// into every per-Sandbox MCP Pod. Without these, the MCP plugin's
// per-tool guards (gitea, domain, storage, keycloak) silently
// degrade to "not configured" because the controller used to emit
// `ORG_ID` / `SOVEREIGN_FQDN` while the MCP binary reads the
// `SANDBOX_*` namespaced variants. Sourced from chart-level env on
// the bp-sandbox HelmRelease (deployment.yaml `runtime.*` + new
// `*Secret` blocks). All fields permit empty — MCP surfaces a clean
// "not configured" error from the affected tool family.
GiteaBaseURL string
GiteaTokenSecretName string
GiteaTokenSecretKey string
DomainAPIURL string
MarketplaceAPIURL string
StorageS3Endpoint string
StorageS3Region string
StorageS3UseTLS string
StorageS3CredsSecretName string
StorageS3AccessKeyKey string
StorageS3SecretKeyKey string
KeycloakAdminURL string
KeycloakParentRealm string
KeycloakAdminTokenSecret string
KeycloakAdminTokenSecretKey string
// Wave 9 — NewAPI bridge client used by Reconcile to mint
// per-Sandbox LLM-gateway tokens (POST /admin/tokens/sandbox,
// PR #1638). When nil the reconciler renders the Wave 1+8
@ -264,6 +289,22 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
EnableHotStandby: r.EnableHotStandby,
PrimaryRegion: r.PrimaryRegion,
ReplicaRegion: r.ReplicaRegion,
// TBD-P4 B4 — canonical SANDBOX_* env-var wiring for MCP plugin.
GiteaBaseURL: r.GiteaBaseURL,
GiteaTokenSecretName: r.GiteaTokenSecretName,
GiteaTokenSecretKey: r.GiteaTokenSecretKey,
DomainAPIURL: r.DomainAPIURL,
MarketplaceAPIURL: r.MarketplaceAPIURL,
StorageS3Endpoint: r.StorageS3Endpoint,
StorageS3Region: r.StorageS3Region,
StorageS3UseTLS: r.StorageS3UseTLS,
StorageS3CredsSecretName: r.StorageS3CredsSecretName,
StorageS3AccessKeyKey: r.StorageS3AccessKeyKey,
StorageS3SecretKeyKey: r.StorageS3SecretKeyKey,
KeycloakAdminURL: r.KeycloakAdminURL,
KeycloakParentRealm: r.KeycloakParentRealm,
KeycloakAdminTokenSecret: r.KeycloakAdminTokenSecret,
KeycloakAdminTokenSecretKey: r.KeycloakAdminTokenSecretKey,
}
manifests, err := gitops.Render(in)
if err != nil {

View File

@ -211,6 +211,22 @@ func makeReconciler(t *testing.T, objs ...client.Object) (*Reconciler, *giteaSer
LLMGatewayTokenSecret: "sandbox-tokens",
BYOSSecretPrefix: "sandbox-byos-claude-code",
IdleTimeoutMinutes: 30,
// TBD-P4 B4 — canonical SANDBOX_* env-var wiring (chart defaults).
GiteaBaseURL: "http://gitea-http.gitea.svc.cluster.local:3000",
GiteaTokenSecretName: "catalyst-gitea-token",
GiteaTokenSecretKey: "token",
DomainAPIURL: "http://domain.sme.svc.cluster.local:8086",
MarketplaceAPIURL: "http://marketplace-api.marketplace.svc.cluster.local:8082",
StorageS3Endpoint: "http://seaweedfs.storage.svc.cluster.local:8333",
StorageS3Region: "us-east-1",
StorageS3UseTLS: "false",
StorageS3CredsSecretName: "sandbox-storage-s3",
StorageS3AccessKeyKey: "AWS_ACCESS_KEY_ID",
StorageS3SecretKeyKey: "AWS_SECRET_ACCESS_KEY",
KeycloakAdminURL: "http://keycloak.keycloak.svc.cluster.local:8080",
KeycloakParentRealm: "master",
KeycloakAdminTokenSecret: "keycloak-admin-token",
KeycloakAdminTokenSecretKey: "token",
}
return r, gs
}
@ -465,12 +481,61 @@ func TestReconcile_Wave8RuntimeShape(t *testing.T) {
`image: "ghcr.io/openova-io/openova/sandbox-mcp:test-sha"`,
"PTY_SERVER_URL",
"pty-server.sandbox-ceo-at-acme-com.svc.cluster.local:7681",
// TBD-P4 B4 regression — the MCP Deployment MUST emit the
// canonical SANDBOX_* env-var set the MCP plugin's
// products/sandbox/mcp-server/internal/tools/env.go reads.
// Before this slice the controller emitted bare `ORG_ID` and
// `SOVEREIGN_FQDN` on the MCP Pod → MCP read the wrong keys →
// every tool family silently degraded to "not configured" at
// runtime. Asserting on the canonical names here prevents the
// drift from reappearing.
"name: SANDBOX_ORG_ID",
"name: SANDBOX_SOVEREIGN_FQDN",
"name: SANDBOX_ID",
"name: SANDBOX_NAMESPACE",
"name: SANDBOX_TENANT_ID",
"name: SANDBOX_GITEA_BASE_URL",
"name: SANDBOX_GITEA_TOKEN",
"name: SANDBOX_DOMAIN_API_URL",
"name: SANDBOX_MARKETPLACE_API_URL",
"name: SANDBOX_STORAGE_S3_ENDPOINT",
"name: SANDBOX_STORAGE_S3_REGION",
"name: SANDBOX_STORAGE_S3_USE_TLS",
"name: SANDBOX_STORAGE_S3_ACCESS_KEY",
"name: SANDBOX_STORAGE_S3_SECRET_KEY",
"name: KEYCLOAK_ADMIN_URL",
"name: KEYCLOAK_PARENT_REALM",
"name: KEYCLOAK_ADMIN_TOKEN",
// Values plumbed from the controller's chart-level env.
"http://gitea-http.gitea.svc.cluster.local:3000",
"http://domain.sme.svc.cluster.local:8086",
"http://seaweedfs.storage.svc.cluster.local:8333",
"http://keycloak.keycloak.svc.cluster.local:8080",
`name: "catalyst-gitea-token"`,
`name: "sandbox-storage-s3"`,
`name: "keycloak-admin-token"`,
} {
if !strings.Contains(dep, want) {
t.Errorf("deployment-mcp.yaml missing %q", want)
}
}
// TBD-P4 B4 — the OLD bare names MUST NOT appear on the MCP
// Deployment. They remain on the pty-server StatefulSet (inherited
// by user shells, distinct contract). Any future renderer change
// that puts them back onto the MCP Deployment regresses the MCP
// plugin's tool gates, so the negative assertion is load-bearing.
for _, banned := range []string{
"- name: ORG_ID\n",
"- name: SOVEREIGN_FQDN\n",
} {
if strings.Contains(dep, banned) {
t.Errorf("deployment-mcp.yaml MUST NOT contain bare %q "+
"(MCP plugin reads canonical SANDBOX_ORG_ID / SANDBOX_SOVEREIGN_FQDN)",
strings.TrimSpace(banned))
}
}
svc := get("service-pty-server.yaml")
for _, want := range []string{
"kind: Service",

View File

@ -94,6 +94,35 @@ type Inputs struct {
EnableHotStandby string
PrimaryRegion string
ReplicaRegion string
// TBD-P4 B4 — canonical SANDBOX_* env-var wiring for the MCP plugin
// (products/sandbox/mcp-server/internal/tools/env.go). Without these,
// every tool family (gitea / domain / storage / keycloak) silently
// degrades to "not configured" at call time because the controller
// previously emitted bare `ORG_ID` / `SOVEREIGN_FQDN` while the MCP
// binary reads `SANDBOX_ORG_ID` / `SANDBOX_SOVEREIGN_FQDN` etc.
//
// Each value is plumbed by the controller from its chart-level env
// (deployment.yaml `runtime.*` + new `*Secret` blocks). Empty leaves
// the canonical var as an empty string on the MCP Pod, which the
// MCP's per-tool requireX guard surfaces as a clear "not configured"
// error — same behaviour as before, just now reachable instead of
// silently misnamed.
GiteaBaseURL string
GiteaTokenSecretName string
GiteaTokenSecretKey string
DomainAPIURL string
MarketplaceAPIURL string
StorageS3Endpoint string
StorageS3Region string
StorageS3UseTLS string
StorageS3CredsSecretName string
StorageS3AccessKeyKey string
StorageS3SecretKeyKey string
KeycloakAdminURL string
KeycloakParentRealm string
KeycloakAdminTokenSecret string
KeycloakAdminTokenSecretKey string
}
const namespaceTemplate = `apiVersion: v1
@ -409,10 +438,85 @@ spec:
value: {{ .OwnerUID | quote }}
- name: SANDBOX_OWNER_EMAIL
value: {{ .OwnerEmail | quote }}
- name: ORG_ID
# TBD-P4 B4 canonical SANDBOX_* names the MCP plugin
# reads (products/sandbox/mcp-server/internal/tools/env.go).
# ORG_ID + SOVEREIGN_FQDN were the original names and
# silently degraded every MCP tool family to "not configured".
- name: SANDBOX_ORG_ID
value: {{ .OrgSlug | quote }}
- name: SOVEREIGN_FQDN
- name: SANDBOX_SOVEREIGN_FQDN
value: {{ .SovereignFQDN | quote }}
- name: SANDBOX_ID
value: {{ .Name | quote }}
- name: SANDBOX_NAMESPACE
value: {{ .NamespaceName | quote }}
# SANDBOX_TENANT_ID scopes the MCP's domain/byod handler
# (marketplace.go:93). The per-Org slug is the tenant key in
# the chroot Organization controller's wiring; the MCP
# treats this opaquely.
- name: SANDBOX_TENANT_ID
value: {{ .OrgSlug | quote }}
# Gitea wiring SANDBOX_GITEA_BASE_URL is unauthed in
# the in-cluster path; the matching token is mounted from
# the existing catalyst-gitea-token Secret (single source
# of truth shared with the controller itself; never written
# here per Inviolable Principle #4 Secrets are owned by
# bp-catalyst-platform seed jobs).
- name: SANDBOX_GITEA_BASE_URL
value: {{ .GiteaBaseURL | quote }}
{{- if .GiteaTokenSecretName }}
- name: SANDBOX_GITEA_TOKEN
valueFrom:
secretKeyRef:
name: {{ .GiteaTokenSecretName | quote }}
key: {{ .GiteaTokenSecretKey | quote }}
optional: true
{{- end }}
# Domain + marketplace REST surfaces (services that
# already run in-cluster; per-Sovereign overlays may pin
# alternate ClusterIPs via the bp-sandbox HR values).
- name: SANDBOX_DOMAIN_API_URL
value: {{ .DomainAPIURL | quote }}
- name: SANDBOX_MARKETPLACE_API_URL
value: {{ .MarketplaceAPIURL | quote }}
# Storage (SeaweedFS S3). Endpoint + region are public;
# credentials are sourced from an existing per-Sandbox IAM
# Secret when present. Empty creds surface a clear "not
# configured" error from the MCP's storage tool family.
- name: SANDBOX_STORAGE_S3_ENDPOINT
value: {{ .StorageS3Endpoint | quote }}
- name: SANDBOX_STORAGE_S3_REGION
value: {{ .StorageS3Region | quote }}
- name: SANDBOX_STORAGE_S3_USE_TLS
value: {{ .StorageS3UseTLS | quote }}
{{- if .StorageS3CredsSecretName }}
- name: SANDBOX_STORAGE_S3_ACCESS_KEY
valueFrom:
secretKeyRef:
name: {{ .StorageS3CredsSecretName | quote }}
key: {{ .StorageS3AccessKeyKey | quote }}
optional: true
- name: SANDBOX_STORAGE_S3_SECRET_KEY
valueFrom:
secretKeyRef:
name: {{ .StorageS3CredsSecretName | quote }}
key: {{ .StorageS3SecretKeyKey | quote }}
optional: true
{{- end }}
# Keycloak admin surface. URL + parent realm are public;
# the admin bearer is sourced from an existing Secret.
- name: KEYCLOAK_ADMIN_URL
value: {{ .KeycloakAdminURL | quote }}
- name: KEYCLOAK_PARENT_REALM
value: {{ .KeycloakParentRealm | quote }}
{{- if .KeycloakAdminTokenSecret }}
- name: KEYCLOAK_ADMIN_TOKEN
valueFrom:
secretKeyRef:
name: {{ .KeycloakAdminTokenSecret | quote }}
key: {{ .KeycloakAdminTokenSecretKey | quote }}
optional: true
{{- end }}
- name: PTY_SERVER_URL
value: "http://pty-server.{{ .NamespaceName }}.svc.cluster.local:7681"
- name: LLM_GATEWAY_TOKEN

View File

@ -24,8 +24,8 @@ annotations:
# (see issue #181 + docs/BLUEPRINT-AUTHORING.md §11.1).
catalyst.openova.io/no-upstream: "true"
catalyst.openova.io/smoke-render-mode: "default-off"
version: 0.1.0
appVersion: "0.1.0"
version: 0.2.0
appVersion: "0.2.0"
keywords:
- catalyst
- sandbox

View File

@ -112,6 +112,52 @@ spec:
value: {{ (.Values.cnpg).activeHotStandby.primaryRegion | default "" | quote }}
- name: SOVEREIGN_REPLICA_REGION
value: {{ (.Values.cnpg).activeHotStandby.replicaRegion | default "" | quote }}
# TBD-P4 B4 — canonical SANDBOX_* env-var wiring for the MCP
# plugin. The controller passes these straight through to
# every per-Sandbox MCP Deployment via gitops.Inputs so the
# plugin's per-tool guards (gitea / domain / storage /
# keycloak) see the canonical names defined in
# products/sandbox/mcp-server/internal/tools/env.go. Before
# this slice the renderer emitted `ORG_ID` / `SOVEREIGN_FQDN`
# while the MCP read `SANDBOX_ORG_ID` / `SANDBOX_SOVEREIGN_FQDN`
# → every tool family silently degraded to "not configured".
#
# Values are env vars on the CONTROLLER (this Deployment);
# the controller reads each and passes via gitops.Inputs into
# the rendered MCP Deployment template. Secrets are NEVER
# written from the controller — name + key references only,
# `optional: true` on the per-Sandbox secretKeyRef so missing
# secrets surface a clean "not configured" error from the MCP.
- name: SANDBOX_MCP_GITEA_BASE_URL
value: {{ .Values.mcp.giteaBaseURL | default "http://gitea-http.gitea.svc.cluster.local:3000" | quote }}
- name: SANDBOX_MCP_GITEA_TOKEN_SECRET_NAME
value: {{ .Values.mcp.giteaTokenSecret.name | default "catalyst-gitea-token" | quote }}
- name: SANDBOX_MCP_GITEA_TOKEN_SECRET_KEY
value: {{ .Values.mcp.giteaTokenSecret.key | default "token" | quote }}
- name: SANDBOX_MCP_DOMAIN_API_URL
value: {{ .Values.mcp.domainAPIURL | default "http://domain.sme.svc.cluster.local:8086" | quote }}
- name: SANDBOX_MCP_MARKETPLACE_API_URL
value: {{ .Values.mcp.marketplaceAPIURL | default "http://marketplace-api.marketplace.svc.cluster.local:8082" | quote }}
- name: SANDBOX_MCP_STORAGE_S3_ENDPOINT
value: {{ .Values.mcp.storage.s3Endpoint | default "http://seaweedfs.storage.svc.cluster.local:8333" | quote }}
- name: SANDBOX_MCP_STORAGE_S3_REGION
value: {{ .Values.mcp.storage.s3Region | default "us-east-1" | quote }}
- name: SANDBOX_MCP_STORAGE_S3_USE_TLS
value: {{ .Values.mcp.storage.s3UseTLS | default "false" | quote }}
- name: SANDBOX_MCP_STORAGE_S3_CREDS_SECRET_NAME
value: {{ .Values.mcp.storage.credsSecret.name | default "" | quote }}
- name: SANDBOX_MCP_STORAGE_S3_ACCESS_KEY_KEY
value: {{ .Values.mcp.storage.credsSecret.accessKeyKey | default "AWS_ACCESS_KEY_ID" | quote }}
- name: SANDBOX_MCP_STORAGE_S3_SECRET_KEY_KEY
value: {{ .Values.mcp.storage.credsSecret.secretKeyKey | default "AWS_SECRET_ACCESS_KEY" | quote }}
- name: SANDBOX_MCP_KEYCLOAK_ADMIN_URL
value: {{ .Values.mcp.keycloak.adminURL | default "http://keycloak.keycloak.svc.cluster.local:8080" | quote }}
- name: SANDBOX_MCP_KEYCLOAK_PARENT_REALM
value: {{ .Values.mcp.keycloak.parentRealm | default "master" | quote }}
- name: SANDBOX_MCP_KEYCLOAK_ADMIN_TOKEN_SECRET_NAME
value: {{ .Values.mcp.keycloak.adminTokenSecret.name | default "" | quote }}
- name: SANDBOX_MCP_KEYCLOAK_ADMIN_TOKEN_SECRET_KEY
value: {{ .Values.mcp.keycloak.adminTokenSecret.key | default "token" | quote }}
{{- range $k, $v := .Values.extraEnv }}
- name: {{ $k | quote }}
value: {{ $v | quote }}

View File

@ -186,4 +186,45 @@ serviceMonitor:
labels: {}
# namespace override — defaults to .Release.Namespace.
namespace: ""
# TBD-P4 B4 — canonical SANDBOX_* env-var wiring for the MCP plugin
# (products/sandbox/mcp-server/internal/tools/env.go). Surfaced by the
# controller as env vars; the controller in turn passes each through to
# every per-Sandbox MCP Deployment via gitops.Inputs so the MCP plugin's
# per-tool guards (gitea / domain / storage / keycloak) see the canonical
# names instead of the legacy `ORG_ID` / `SOVEREIGN_FQDN` shape.
#
# Defaults match the in-cluster Service DNS for a stock Sovereign install
# (bp-gitea / bp-newapi / bp-seaweedfs / bp-keycloak / bp-marketplace).
# Per-Sovereign overlays may override any value. Secrets are NEVER written
# from this chart — name+key references only with `optional: true` so a
# fresh-prov Sovereign with a credential source still in flight does NOT
# crash the per-Sandbox MCP Pod; the affected tool family surfaces a clean
# "not configured" error.
mcp:
giteaBaseURL: "http://gitea-http.gitea.svc.cluster.local:3000"
giteaTokenSecret:
name: "catalyst-gitea-token"
key: "token"
domainAPIURL: "http://domain.sme.svc.cluster.local:8086"
marketplaceAPIURL: "http://marketplace-api.marketplace.svc.cluster.local:8082"
storage:
s3Endpoint: "http://seaweedfs.storage.svc.cluster.local:8333"
s3Region: "us-east-1"
s3UseTLS: "false"
credsSecret:
# Empty default — when set, the controller stamps SANDBOX_STORAGE_S3_
# ACCESS_KEY / SECRET_KEY secretKeyRefs on every per-Sandbox MCP Pod.
# Empty leaves the MCP storage tool family in "not configured" mode.
name: ""
accessKeyKey: "AWS_ACCESS_KEY_ID"
secretKeyKey: "AWS_SECRET_ACCESS_KEY"
keycloak:
adminURL: "http://keycloak.keycloak.svc.cluster.local:8080"
parentRealm: "master"
adminTokenSecret:
# Empty default — when set, the controller stamps a KEYCLOAK_ADMIN_TOKEN
# secretKeyRef on every per-Sandbox MCP Pod. Empty leaves the MCP
# keycloak tool family in "not configured" mode.
name: ""
key: "token"
extraEnv: {}