Skip to content

Add MCPServerEntry backend discovery to VirtualMCPServer#4698

Merged
ChrisJBurns merged 5 commits intomainfrom
worktree-issue-4657-vmcp-entry-discovery
Apr 9, 2026
Merged

Add MCPServerEntry backend discovery to VirtualMCPServer#4698
ChrisJBurns merged 5 commits intomainfrom
worktree-issue-4657-vmcp-entry-discovery

Conversation

@JAORMX
Copy link
Copy Markdown
Collaborator

@JAORMX JAORMX commented Apr 9, 2026

Summary

  • VirtualMCPServer needs to discover MCPServerEntry resources (zero-infrastructure catalog entries) and route traffic to remote MCP servers without proxy pods. This is the Phase 2 story from RFC-0055.
  • Extends the workload discoverer, ConfigMap generation, RBAC, watches, and deployment volumes to support MCPServerEntry as a new backend type alongside MCPServer and MCPRemoteProxy.

Closes #4657

Type of change

  • New feature

Test plan

  • Unit tests (task test)
  • Linting (task lint-fix)

Changes

File Change
pkg/vmcp/workloads/discoverer.go Add WorkloadTypeMCPServerEntry constant
pkg/vmcp/workloads/k8s.go Add MCPServerEntry listing, backend conversion, health mapping, auth discovery; extract metadata key constants
cmd/thv-operator/controllers/virtualmcpserver_controller.go Add listMCPServerEntriesAsMap, mapMCPServerEntryToVirtualMCPServer watch mapper, MCPServerEntry support in auth config discovery, RBAC markers
cmd/thv-operator/controllers/virtualmcpserver_deployment.go Add mcpserverentries to discovered RBAC rules, CA bundle volume helpers, buildCABundleVolumesForEntries
cmd/thv-operator/controllers/virtualmcpserver_vmcpconfig.go Extend buildTransportMap for entry type, add buildCABundlePathMap, pass CA bundle paths to static config
pkg/vmcp/config/config.go Add CABundlePath field to StaticBackendConfig
deploy/charts/operator-crds/... Regenerated CRD manifests for new field
docs/operator/crd-api.md Regenerated CRD API docs

Large PR Justification

  • This is a single atomic feature (MCPServerEntry backend discovery) that touches the same code paths as the existing MCPServer and MCPRemoteProxy patterns. Splitting would break the feature into non-functional pieces.
  • ~460 lines of production code across 6 files, plus ~1200 lines of comprehensive unit tests and ~13 lines of generated CRD manifests/docs. The bulk of the diff is tests and generated code.
  • Each touch point (discoverer, ConfigMap generation, RBAC, watches, CA bundle volumes) depends on the others to form a working feature.

Does this introduce a user-facing change?

VirtualMCPServer now discovers MCPServerEntry resources via groupRef and includes them as backends in the generated ConfigMap. Users can declare remote MCP server endpoints as MCPServerEntry CRDs and have them automatically routed through vMCP.

Special notes for reviewers

  • MCPServerEntry uses spec.remoteURL directly (unlike MCPServer/MCPRemoteProxy which use status.URL set after K8s Service creation), since it creates no infrastructure.
  • CA bundle ConfigMaps are mounted read-only at /etc/toolhive/ca-bundles/<entry-name>/ with the key defaulting to ca.crt.
  • Also fixes a pre-existing missing mcpremoteproxies RBAC marker on the VirtualMCPServer controller.
  • The redundant listMCPServerEntriesAsMap calls (one per helper function) mirror the existing pattern for MCPServer/MCPRemoteProxy; a caching pass is deferred to a follow-up optimization.

Generated with Claude Code

@github-actions github-actions bot added the size/XL Extra large PR: 1000+ lines changed label Apr 9, 2026
Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

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 transformation

Alternative:

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.

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 9, 2026

Codecov Report

❌ Patch coverage is 67.85714% with 90 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.63%. Comparing base (55d82f5) to head (e80fc28).
⚠️ Report is 9 commits behind head on main.

Files with missing lines Patch % Lines
...perator/controllers/virtualmcpserver_controller.go 25.37% 48 Missing and 2 partials ⚠️
pkg/vmcp/workloads/k8s.go 82.40% 15 Missing and 4 partials ⚠️
...perator/controllers/virtualmcpserver_deployment.go 84.00% 9 Missing and 3 partials ⚠️
...perator/controllers/virtualmcpserver_vmcpconfig.go 70.00% 6 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4698      +/-   ##
==========================================
- Coverage   68.68%   68.63%   -0.05%     
==========================================
  Files         515      516       +1     
  Lines       53567    54113     +546     
==========================================
+ Hits        36792    37142     +350     
- Misses      13932    14106     +174     
- Partials     2843     2865      +22     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

JAORMX and others added 2 commits April 9, 2026 14:19
VirtualMCPServer needs to discover MCPServerEntry resources (zero-infrastructure
catalog entries) and include them as backends. This enables vMCP to route traffic
to remote MCP servers declared as MCPServerEntry without proxy pods.

- Add WorkloadTypeMCPServerEntry constant and discoverer logic that lists
  MCPServerEntries by groupRef, converts them to vmcp.Backend using the remote
  URL directly from the spec (no K8s Service needed)
- Extend ConfigMap generation to include entry-type backends with remoteURL,
  transport, auth config, and CA bundle mount paths
- Mount CA bundle ConfigMaps as read-only volumes at
  /etc/toolhive/ca-bundles/<entry-name>/ when caBundleRef is configured
- Add mcpserverentries to VirtualMCPServer RBAC rules (get/list/watch)
- Add MCPServerEntry watch with optimized mapper (via MCPGroup Status.Entries)
- Add CABundlePath field to StaticBackendConfig for TLS verification
- Fix pre-existing missing mcpremoteproxies RBAC marker on controller
- Extract metadata key constants to fix goconst lint violations

Closes #4657

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Run task operator-manifests, operator-generate, and crdref-gen to
pick up the new CABundlePath field on StaticBackendConfig and the
updated RBAC markers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@JAORMX JAORMX force-pushed the worktree-issue-4657-vmcp-entry-discovery branch from e94dc73 to 6a67884 Compare April 9, 2026 11:22
@github-actions github-actions bot added size/XL Extra large PR: 1000+ lines changed and removed size/XL Extra large PR: 1000+ lines changed labels Apr 9, 2026
@github-actions github-actions bot dismissed their stale review April 9, 2026 11:23

Large PR justification has been provided. Thank you!

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

✅ Large PR justification has been provided. The size review has been dismissed and this PR can now proceed with normal review.

@github-actions github-actions bot added size/XL Extra large PR: 1000+ lines changed and removed size/XL Extra large PR: 1000+ lines changed labels Apr 9, 2026
Copy link
Copy Markdown
Collaborator

@ChrisJBurns ChrisJBurns left a comment

Choose a reason for hiding this comment

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

Multi-Agent Consensus Review

Agents consulted: kubernetes-expert, code-quality-reviewer, security-advisor, toolhive-expert

Consensus Summary

# Finding Consensus Severity Action
1 CABundlePath plumbed but never consumed by vMCP runtime 10/10 HIGH Document or implement
2 caBundleVolumeName truncation collision risk 10/10 MEDIUM Fix
3 listMCPServerEntriesAsMap called 5x per reconciliation 8/10 MEDIUM Track as tech debt
4 metadataKeyWorkloadStat truncated name 7/10 LOW Fix
5 caBundleMountPath nil dereference risk 7/10 LOW Fix
6 RemoteURL lacks runtime validation 7/10 MEDIUM Verify CRD validation
7 buildCABundlePathMap silently swallows errors 7/10 MEDIUM Fix

Overall

This PR cleanly extends the VirtualMCPServer workload type system with MCPServerEntry as a third backend type. The implementation closely follows existing patterns for MCPServer and MCPRemoteProxy across all integration points — discovery, ConfigMap generation, RBAC, watches, and auth config. Test coverage is thorough with ~1200 lines covering edge cases, auth flows, and the new CA bundle plumbing.

The most significant concern is that the CABundlePath field is fully plumbed through the operator (volumes mounted, paths computed, config populated) but the vMCP runtime never reads it — vmcp.Backend has no CABundlePath field, and no custom TLS config is created. This means CA bundles for MCPServerEntry backends don't actually work yet. If this is intentional staged delivery, the PR description should state this explicitly and link to a follow-up issue. The caBundleVolumeName truncation collision is a concrete bug that should be fixed with a hash suffix before merge.

The remaining findings are improvements rather than blockers: a truncated constant name, a missing nil guard, and an error-swallowing pattern inconsistency.

Documentation

The CRD API docs and manifests are correctly regenerated. If CABundlePath consumption is deferred, a follow-up issue should be created and referenced.


Generated with Claude Code

Copy link
Copy Markdown
Contributor

@jhrozek jhrozek left a comment

Choose a reason for hiding this comment

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

Review comments from automated PR review. 3 findings flagged for discussion.

- Rename metadataKeyWorkloadStat to metadataKeyWorkloadStatus (F4)
- Add nil guard to caBundleMountPath for defensive safety (F5)
- Fix volume name truncation with hash suffix to avoid collisions and
  trailing hyphens in DNS labels (F2)
- Add url.Parse validation for RemoteURL as defense-in-depth (F6)
- Propagate errors from buildCABundlePathMap and
  buildCABundleVolumesForEntries instead of silently swallowing (F7)
- Add explicit phase check to skip non-Valid MCPServerEntry backends
- Use path.Join instead of fmt.Sprintf for path construction
- Add tests for volume name truncation, collision avoidance, and
  phase-based filtering

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions github-actions bot added size/XL Extra large PR: 1000+ lines changed and removed size/XL Extra large PR: 1000+ lines changed labels Apr 9, 2026
ChrisJBurns
ChrisJBurns previously approved these changes Apr 9, 2026
Copy link
Copy Markdown
Collaborator

@ChrisJBurns ChrisJBurns left a comment

Choose a reason for hiding this comment

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

All review feedback from both reviewers has been addressed in 07d0e17:

  • F2/jhrozek: Volume name truncation → hash suffix + trailing hyphen trim + 4 new tests
  • F4: metadataKeyWorkloadStat → renamed to metadataKeyWorkloadStatus
  • F5: caBundleMountPath nil guard → added, plus switched to path.Join
  • F6: RemoteURL runtime validation → url.Parse defense-in-depth added
  • F7/jhrozek: Error handling consistency → buildCABundlePathMap and buildCABundleVolumesForEntries now propagate errors
  • jhrozek: Explicit phase check → non-Valid entries skipped with new test coverage

F1 (CABundlePath not consumed by runtime) remains as intentional staged delivery — acceptable for this PR.
F3 (repeated list calls) acknowledged as tech debt deferred to follow-up.

LGTM.

testify does not provide NotHasSuffix; use
assert.False with strings.HasSuffix instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions github-actions bot added size/XL Extra large PR: 1000+ lines changed and removed size/XL Extra large PR: 1000+ lines changed labels Apr 9, 2026
Rename local `url` variables to `serverURL` and `proxyURL` to avoid
shadowing the `net/url` import, which revive flags as import-shadowing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions github-actions bot added size/XL Extra large PR: 1000+ lines changed and removed size/XL Extra large PR: 1000+ lines changed labels Apr 9, 2026
@ChrisJBurns ChrisJBurns merged commit 97051d8 into main Apr 9, 2026
40 checks passed
@ChrisJBurns ChrisJBurns deleted the worktree-issue-4657-vmcp-entry-discovery branch April 9, 2026 15:48
ChrisJBurns added a commit that referenced this pull request Apr 9, 2026
Main gained a CABundlePath field (after Metadata) via #4698 while this
branch added one (before Metadata, alongside Type). Remove the duplicate
to fix the build.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ChrisJBurns added a commit that referenced this pull request Apr 9, 2026
* Add vMCP MCPServerEntry static backend support

Enable vMCP static mode to parse and connect to MCPServerEntry-type
backends from ConfigMap configuration, routing MCP traffic directly to
remote servers without proxy pods.

- Add BackendType constants (container, proxy, entry) to vmcp types
- Extend StaticBackendConfig with Type and CABundlePath fields
- Propagate entry backend fields through discoverer to Backend
- Add custom CA bundle support to HTTP client transport (per-backend)
- Add config validation for entry backend type and CA bundle paths
- Add unit tests for all new functionality (19 test cases)

Closes #4658

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

* Address review feedback: harden path validation and TLS config

- Strengthen CA bundle path validation with null byte check,
  path traversal rejection, and absolute path requirement to match
  existing patterns in pkg/git/client.go and pkg/server/discovery/
- Clone TLS config instead of replacing it to preserve existing
  settings (e.g. NextProtos for HTTP/2 ALPN) from the cloned transport
- Add backward-compatibility comments to unused BackendType constants
- Add test cases for null bytes and relative paths in CA bundle path

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

* Remove duplicate CABundlePath from StaticBackendConfig

Main gained a CABundlePath field (after Metadata) via #4698 while this
branch added one (before Metadata, alongside Type). Remove the duplicate
to fix the build.

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

* Regenerate CRD manifests and API docs

Update VirtualMCPServer CRD schema to include the new Type field on
StaticBackendConfig. Regenerated with task operator-manifests and
task crdref-gen.

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/XL Extra large PR: 1000+ lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RFC-55: VirtualMCPServer MCPServerEntry Static Mode Backend Discovery

3 participants