Change GroupRef from bare string to typed MCPGroupRef struct#4809
Conversation
GroupRef was a bare string while every other cross-CRD reference
(ExternalAuthConfigRef, ToolConfigRef, AuthServerRef, EmbeddingServerRef)
uses a typed struct. This inconsistency prevented extending GroupRef
with additional fields (like namespace) without a breaking change.
Define MCPGroupRef struct with Name field and nil-safe GetName() helper.
Replace GroupRef string with *MCPGroupRef on MCPServerSpec,
MCPRemoteProxySpec, MCPServerEntrySpec, and add a new top-level GroupRef
field on VirtualMCPServerSpec that takes precedence over the deprecated
config.groupRef string path.
Internal types (vmcpserver.Config, StatusResponse, BackendReconciler,
config.Config) remain as strings since they are not CRD API types.
This is a breaking wire-format change (groupRef: "name" becomes
groupRef: {name: "name"}) that must happen before v1beta1.
Closes #4634
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…-struct # Conflicts: # cmd/thv-operator/api/v1alpha1/virtualmcpserver_types.go # deploy/charts/operator-crds/files/crds/toolhive.stacklok.dev_virtualmcpservers.yaml # deploy/charts/operator-crds/templates/toolhive.stacklok.dev_virtualmcpservers.yaml
There was a problem hiding this comment.
Large PR Detected
This PR exceeds 1000 lines of changes and requires justification before it can be reviewed.
How to unblock this PR:
Add a section to your PR description with the following format:
## Large PR Justification
[Explain why this PR must be large, such as:]
- Generated code that cannot be split
- Large refactoring that must be atomic
- Multiple related changes that would break if separated
- Migration or data transformationAlternative:
Consider splitting this PR into smaller, focused changes (< 1000 lines each) for easier review and reduced risk.
See our Contributing Guidelines for more details.
This review will be automatically dismissed once you add the justification section.
Large PR justification has been provided. Thank you!
|
✅ Large PR justification has been provided. The size review has been dismissed and this PR can now proceed with normal review. |
ChrisJBurns
left a comment
There was a problem hiding this comment.
Multi-Agent Consensus Review
Agents consulted: kubernetes-expert, go-expert-developer, code-reviewer, toolhive-expert
Consensus Summary
| # | Finding | Consensus | Severity | Action |
|---|---|---|---|---|
| 1 | MCPServerEntry validateGroupRef nil guard | 8/10 | MEDIUM | Fix |
| 2 | Missing unit tests for ResolveGroupName/GetName | 8/10 | MEDIUM | Fix |
| 3 | Inconsistent nil-check patterns across indexers | 8/10 | LOW | Discuss |
| 4 | config.Config.Group still marked Required despite deprecation | 7/10 | MEDIUM | Discuss |
| 5 | Validate() error message only mentions spec.groupRef | 7/10 | LOW | Discuss |
Overall
This is a well-executed CRD API refactoring that replaces bare-string GroupRef fields with a typed MCPGroupRef struct across all four CRD types. The design is sound — MCPGroupRef follows the established patterns of ExternalAuthConfigRef, ToolConfigRef, and AuthServerRef, and the nil-safe GetName() helper is a good addition. The ResolveGroupName() deprecation bridge for VirtualMCPServer is clean and correctly preserves backward compatibility.
The diff is large (71 files) but highly mechanical — the production code changes are concentrated in ~13 files with ~116 lines of real logic changes. The consensus findings are mostly about defensive programming (nil guard), test coverage (the new precedence logic is untested), and consistency (mixed nil-check idioms). None are correctness blockers, but F1 and F2 are worth addressing before merge to avoid a potential controller panic and to protect the precedence logic from regressions.
Documentation
The PR updates docs/arch/09-operator-architecture.md and the Kubernetes guide, but several other doc files still reference the old config.groupRef string format: docs/operator/virtualmcpserver-api.md, docs/operator/virtualmcpcompositetooldefinition-guide.md, docs/operator/virtualmcpserver-observability.md. Per CLAUDE.md, task crdref-gen should also be re-run to regenerate docs/operator/crd-api.md. These could be a follow-up PR.
Finding #4 (file-level): config.Config.Group still marked Required
File: pkg/vmcp/config/config.go (not in diff)
Severity: MEDIUM | Consensus: 7/10
The Group field in config.Config retains its +kubebuilder:validation:Required marker, so groupRef remains in the required list of the config object in the CRD schema. Users who set spec.groupRef (the new preferred path) but also need config for telemetry/audit will see a misleading schema that still requires config.groupRef. Consider marking this field +optional and regenerating CRDs.
Raised by: toolhive-expert, kubernetes-expert
Generated with Claude Code
- Add nil guard to MCPServerEntry validateGroupRef using GetName() instead of direct .Name access to prevent potential panic - Add unit tests for MCPGroupRef.GetName() and VirtualMCPServer.ResolveGroupName() covering precedence logic - Normalize nil-check pattern across all field index extractors to use GetName() == "" (handles both nil and empty name) - Mark config.Config.Group as optional and deprecated since spec.groupRef is now the preferred path - Improve Validate() error message to mention both spec.groupRef and config.groupRef paths Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Re Finding #4 (config.Config.Group still marked Required): Fixed in 7412b0f. Changed the Note: The CLI validator ( Re Documentation note: Agreed the other docs ( |
|
Re Finding #4 (config.Config.Group still marked Required): Fixed in 7412b0f. Changed the Note: The CLI validator ( Re Documentation note: Agreed the other docs ( |
|
Hey! Did a thorough review of this one with a few expert lenses (K8s API design, Go code quality, vMCP/docs consistency). The prior review round already caught the important nil-safety and test gaps, and those fixes in Here's what's still standing out to me: 1. Missed example file:
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #4809 +/- ##
==========================================
- Coverage 68.98% 68.93% -0.05%
==========================================
Files 518 518
Lines 54985 55002 +17
==========================================
- Hits 37932 37917 -15
- Misses 14125 14156 +31
- Partials 2928 2929 +1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
- Fix vmcp_with_telemetry_ref.yaml (missed in initial pass) - Update virtualmcpserver-api.md to document spec.groupRef as primary field and update all YAML examples to struct format - Update virtualmcpcompositetooldefinition-guide.md and virtualmcpserver-observability.md examples - Regenerate crd-api.md to show MCPGroupRef typed struct Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Thanks for the thorough review! All 5 points addressed in 3883a63: #1 Missed #2 #3 #4 Pointer for required field on MCPServerEntry: Valid design observation. Using #5 Migration guidance: Added a "Migration Guide" section to the PR description with concrete steps for users to update their manifests. |
Summary
GroupRefwas the only cross-CRD reference using a barestring— every other ref (ExternalAuthConfigRef,ToolConfigRef,AuthServerRef,EmbeddingServerRef) uses a typed struct. This inconsistency prevented extending GroupRef with additional fields (likenamespace) without a breaking change, and blocked struct-level validation markers. This must be fixed before v1beta1.MCPGroupRefstruct withNamefield and nil-safeGetName()helper. ReplaceGroupRef stringwith*MCPGroupRefonMCPServerSpec,MCPRemoteProxySpec,MCPServerEntrySpec. Add a new top-levelGroupRef *MCPGroupReffield onVirtualMCPServerSpec(withResolveGroupName()helper) that takes precedence over the deprecatedconfig.groupRefstring path. Update all controllers, field indexes, backend reconciler, workload discoverer, tests, YAML examples, chainsaw tests, and documentation.Closes #4634
Type of change
Test plan
task test)task lint-fix)go build ./...compiles cleanlytask operator-testpassestask operator-generate && task operator-manifestsregenerates successfullyChanges
cmd/thv-operator/api/v1alpha1/mcpserver_types.goMCPGroupRefstruct withNamefield +GetName()helper; changeMCPServerSpec.GroupReffromstringto*MCPGroupRefcmd/thv-operator/api/v1alpha1/mcpremoteproxy_types.goMCPRemoteProxySpec.GroupReffromstringto*MCPGroupRefcmd/thv-operator/api/v1alpha1/mcpserverentry_types.goMCPServerEntrySpec.GroupReffromstringto*MCPGroupRef; update printcolumn JSONPath to.spec.groupRef.namecmd/thv-operator/api/v1alpha1/virtualmcpserver_types.goGroupRef *MCPGroupReffield; addResolveGroupName()helper; updateValidate()to accept eitherspec.groupReforconfig.groupRefcmd/thv-operator/controllers/*.govalidateGroupRef()methods on all 4 controllers + MCPGroup controller watch handlers to useGetName()/nil checkscmd/thv-operator/main.goGetName()pkg/vmcp/k8s/backend_reconciler.gopkg/vmcp/workloads/k8s.godeploy/charts/operator-crds/examples/operator/docs/Does this introduce a user-facing change?
Yes — this is a breaking wire-format change. The CRD field
groupRefchanges from a bare string to an object:Existing resources with the old format will fail validation against the new CRD schema. Users must update their manifests.
Implementation plan
Approved implementation plan
Design Decision: Option B1
Keep
pkg/vmcp/config/config.goConfig.Groupas a string (platform-agnostic config model shared with CLI). Change all four CRD types to useMCPGroupRef. For VirtualMCPServer, add a top-levelGroupRefthat takes precedence overconfig.groupRef, following the existing pattern whereIncomingAuthsupersedesconfig.IncomingAuth.Internal types (
vmcpserver.Config.GroupRef,StatusResponse.GroupRef,BackendReconciler.GroupRef) stay as strings — they're not CRD API types.Full plan: #4634
Special notes for reviewers
GroupRef: "x"→GroupRef: &MCPGroupRef{Name: "x"}), YAML examples, and documentation.config.Config.Group(used by CLI vMCP) intentionally stays as astring— it's a platform-agnostic model, not a CRD type.spec.config.groupRef(string) is deprecated but still works viaResolveGroupName()for backwards compatibility.Large PR Justification
Generated with Claude Code
Migration Guide
This is a breaking wire-format change for
v1alpha1CRDs. Existing resources in a cluster will fail validation against the new CRD schema after upgrading.What changed: The
groupReffield onMCPServer,MCPRemoteProxy,MCPServerEntry, andVirtualMCPServerchanged from a bare string to a typed struct:How to migrate:
kubectl apply -f deploy/charts/operator-crds/files/crds/MCPServer,MCPRemoteProxy,MCPServerEntry,VirtualMCPServer)For
VirtualMCPServer, movespec.config.groupRefto the new top-levelspec.groupReffield. The deprecatedconfig.groupRefstill works but should be migrated.Large PR Justification
This is an atomic breaking wire-format change that cannot be split without leaving CRDs in an inconsistent intermediate state. The production code changes are ~116 lines across 13 files. The remaining diff is generated CRD YAMLs, mechanical test updates, YAML examples, and documentation — all review-exempt categories.