Skip to content

RFC: Decouple the VirtualMCPServer CRD schema from the vMCP config model#78

Open
ChrisJBurns wants to merge 6 commits into
mainfrom
cburns/rfc-decouple-vmcp-config-from-crd
Open

RFC: Decouple the VirtualMCPServer CRD schema from the vMCP config model#78
ChrisJBurns wants to merge 6 commits into
mainfrom
cburns/rfc-decouple-vmcp-config-from-crd

Conversation

@ChrisJBurns

Copy link
Copy Markdown
Contributor

Summary

Proposes decoupling the VirtualMCPServer CRD's spec.config from the internal
pkg/vmcp/config model. Today they are the same Go type, so controller-gen
walks the internal config tree into the public CRD — meaning any internal config
change leaks into the public API. This RFC introduces an operator-owned mirror
type
plus a converter seam, delivered as an incremental, provably
non-breaking migration
guarded by drift tests.

Why an RFC

The full change is large and hard to review as one diff. This RFC frames the
direction — and, importantly, the two-phase, one-subtree-per-PR migration
so reviewers can see that the implementation PR is just step 1a of a deliberate,
gated sequence, not a big bang.

Highlights

  • Zero user-facing change for the mechanical decoupling (byte-identical CRD,
    enforced by a zero-diff gate on every PR).
  • Drift is caught automatically — structural parity, round-trip transcode
    fuzz, and a categorical no-leak boundary test.
  • Follows the established Kubernetes internal-vs-versioned types pattern; the
    telemetry path (spectoconfig) is the existing in-repo template.
  • Phase 1 = mechanical decoupling (non-breaking, incremental). Phase 2 =
    the actual API evolution (Kubernetes-native refs, deprecating duplicate fields,
    v1beta2 if needed) — the only place real user-facing change happens.

Implementation

Phase 1a is implemented in toolhive#5238.

🤖 Generated with Claude Code

ChrisJBurns and others added 3 commits June 23, 2026 16:20
Proposes decoupling the VirtualMCPServer CRD schema from the internal
pkg/vmcp/config model via an operator-owned mirror type and a converter
seam, delivered as an incremental, zero-diff, drift-tested migration.
Phase 1 implementation is toolhive#5238.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The coupling this RFC addresses was a deliberate decision in THV-0023
(toolhive-rfcs#27), which recommended unifying CRD spec types with
application config types. Add that lineage to the Summary, Problem
Statement, References, and metadata, expand Alternative 2 into a balanced
treatment of unified types, and state that this RFC supersedes that
section's recommendation — keeping its single-source-of-truth goal while
removing the API coupling via enforced-equivalence tests instead of type
identity.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Incorporate review feedback (#2#7):
- #2: soften the equivalence claim — the tests catch structural and
  value-loss drift but NOT marker/CEL/enum/default/doc-comment drift;
  name that as accepted residual risk with the mirror's markers as SoT,
  and promote code-gen (Alt 3) to the recommended end-state that closes it.
- #3: stop "superseding" a Draft — revisits THV-0023 (still Draft) and the
  implementation that adopted it.
- #4: "categorical" is an end-state property; the no-leak boundary widens
  one package per PR (after 1a it bars only pkg/vmcp/config).
- #5: lead the precedent with the in-repo spectoconfig converter; demote
  the k8s internal-vs-versioned analogy to "loosely analogous".
- #6: add operator-generate (deepcopy) to the zero-diff gate; clarify it
  proves schema + generated-code, not converter behaviour; note order
  sensitivity.
- #7: make RateLimitConfig a single cross-CRD cutover (MCPServer + vMCP) to
  avoid transient two-source divergence per-CRD tests can't catch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ChrisJBurns

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review — all six points addressed in 544f815.

  • Move RFCs to correct folder #2 (HIGH, equivalence overstated): Softened. The tests are now scoped as catching structural (parity) and value-loss (round-trip) drift only; the Summary/Goals/Alternative 2/Testing Strategy now state plainly that marker/CEL/enum/default/doc-comment parity is not covered and is accepted residual risk, with the mirror's markers named as the single source of truth for CRD validation. Code-gen (Alternative 3) is promoted to recommended end-state as the only thing that closes this gap, cited explicitly from Alternative 2.
  • Port THV-1497 #3 (superseding a Draft): Reworded throughout — it now "revisits the unified-types recommendation in THV-0023 (which remains in Draft) and the implementation that adopted it," and the metadata Supersedes: became Revisits:.
  • Port THV-1566 #4 ("categorical" only at end-state): Goals now frame "categorically cannot reach the CRD" as an end-state property; added that the no-leak boundary widens one package per PR (after 1a it bars only pkg/vmcp/config; audit/ratelimit/telemetry/auth-types still get walked until mirrored).
  • Port THV-1592 #5 (inapt k8s precedent): References now lead with the in-repo spectoconfig converter as the precise precedent; the k8s internal-vs-versioned analogy is demoted to "loosely analogous" (version-conversion via conversion-gen vs same-version cross-domain transcode; only the round-trip-fuzz methodology is borrowed).
  • Port THV-1681 #6 (zero-diff gate insufficient): Added task operator-generate (deepcopy) to the gate; clarified it proves schema + generated-code stability but not converter behaviour (covered by round-trip fuzz + envtest/e2e), and noted marker-order sensitivity.
  • Port THV-2063 #7 (cross-CRD RateLimitConfig divergence): Phase 1c is now an explicit single cross-CRD cutover (MCPServer + VirtualMCPServer in one PR), since mirroring vMCP alone would leave two sources generating the same schema — a transient divergence per-CRD drift tests can't catch; a cross-CRD parity test is named as the fallback if they must be split.

ChrisJBurns and others added 3 commits June 23, 2026 16:53
PR #4923 adds an operator-resolved CA-bundle path to config.OIDCConfig and,
because that type is embedded in the CRD, leaks 10 lines into the public
VirtualMCPServer CRD schema for a value no user sets by hand — a live
instance of the "can't add operator-resolved fields without them hitting
the CRD" problem. Add it to the Problem Statement and References.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Capture the THV-0023 author's review proposal: share config component
types across the CRD and internal config, differing only at the assembling
structs. Present it fairly (minimal translation, less duplication, no
marker gap for shared parts) with its honest tradeoff (shared leaves stay
reachable from the CRD, so not categorical) and as largely compatible with
this RFC, pointing at a likely hybrid end-state. Add the central open
question it forces: zero coupling vs. zero unintentional coupling.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per review feedback: config.Config is itself a public surface (the on-disk
config.yaml schema and the type library consumers configure directly), not
an internal implementation detail. Stop calling it internal in the title,
Summary, Problem Statement, and Alternative 2; frame the coupling as welding
two public contracts (CRD + library/runtime config API) into one Go type.
Add library consumers as a stakeholder, note the CRD is not 1:1 with the
on-disk config, and add a goal to document/version config.Config as a
first-class public schema (cf. RunConfig).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ChrisJBurns ChrisJBurns changed the title RFC: Decouple the VirtualMCPServer CRD schema from the internal vMCP config model RFC: Decouple the VirtualMCPServer CRD schema from the vMCP config model Jun 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant