Background
MCPServer and MCPRemoteProxy already support any registered authz backend (cedarv1, httpv1, etc.) via authzConfigRef — the runner's authz middleware dispatches on type through a pluggable registry.
VirtualMCPServer does not. Its runtime (vmcp binary) has its own incoming-auth pipeline that was only ever built for Cedar. Even after #5580 wires authzConfigRef into the VirtualMCPServer controller, the controller explicitly rejects non-Cedar refs with a clear error — because passing them through would be inert and fail later with an opaque message.
This issue tracks making VirtualMCPServer's runtime authz pluggable so it reaches parity with the other two workload types.
Simple explanation
Think of MCPServer and MCPRemoteProxy as dumb proxies — they hand any authz config blob to a pluggable system that looks up the right enforcer by type. VirtualMCPServer runs its own binary (vmcp) which has its own separate auth pipeline hard-coded to Cedar. The Cedar-specific fields are baked into the vMCP config struct, and the factory that creates auth middleware just checks if type == "cedar" with no way to plug in anything else.
The result is:
| Workload |
cedarv1 |
httpv1 |
| MCPServer |
✓ |
✓ |
| MCPRemoteProxy |
✓ |
✓ |
| VirtualMCPServer |
✓ |
✗ (fails fast, clear error) |
This issue closes the gap for VirtualMCPServer.
Technical detail
What's hard-coded today
pkg/vmcp/config.AuthzConfig has Cedar-specific fields baked in:
type AuthzConfig struct {
Type string // only "cedar" or "none" meaningful today
Policies []string // Cedar-specific
EntitiesJSON string // Cedar-specific
GroupClaimName string // Cedar-specific
RoleClaimName string // Cedar-specific
GroupEntityType string // Cedar-specific
}
There is no way to represent an HTTP PDP authorizer in this struct.
pkg/vmcp/auth/factory/incoming.go:93 hard-codes the Cedar check:
if cfg.Authz != nil && cfg.Authz.Type == "cedar" && len(cfg.Authz.Policies) > 0 {
authzMiddleware, err = newCedarAuthzMiddleware(cfg.Authz, serverName, passThroughTools)
}
No dispatch, no registry, no fallback.
What needs to change
-
pkg/vmcp/config.AuthzConfig — replace the Cedar-specific fields with a generic backend blob, mirroring how pkg/authz.Config works for the runner (a Type string + a RawExtension-style opaque config). The existing Cedar fields can be retained inside a Cedar-specific sub-struct or migrated to the blob representation.
-
pkg/vmcp/auth/factory/incoming.go — replace the if type == "cedar" check with a dispatch through the authorizer registry (pkg/authz/authorizers), so any registered backend can be resolved at runtime.
-
pkg/vmcp/config validator — update validateAuthz to accept the new generic shape and validate the backend-specific blob against the registered factory.
-
cmd/thv-operator/pkg/vmcpconfig/converter.go — once the runtime is pluggable, resolveAuthzConfigRef can resolve any registered backend (remove the cedarv1-only gate). The controller's handleAuthzConfig cedarv1 check in virtualmcpserver_controller.go can also be removed.
-
cmd/thv-operator/pkg/vmcpconfig validator — align with the new config shape.
References
Background
MCPServerandMCPRemoteProxyalready support any registered authz backend (cedarv1,httpv1, etc.) viaauthzConfigRef— the runner's authz middleware dispatches on type through a pluggable registry.VirtualMCPServerdoes not. Its runtime (vmcpbinary) has its own incoming-auth pipeline that was only ever built for Cedar. Even after #5580 wiresauthzConfigRefinto the VirtualMCPServer controller, the controller explicitly rejects non-Cedar refs with a clear error — because passing them through would be inert and fail later with an opaque message.This issue tracks making VirtualMCPServer's runtime authz pluggable so it reaches parity with the other two workload types.
Simple explanation
Think of
MCPServerandMCPRemoteProxyas dumb proxies — they hand any authz config blob to a pluggable system that looks up the right enforcer by type.VirtualMCPServerruns its own binary (vmcp) which has its own separate auth pipeline hard-coded to Cedar. The Cedar-specific fields are baked into the vMCP config struct, and the factory that creates auth middleware just checksif type == "cedar"with no way to plug in anything else.The result is:
This issue closes the gap for VirtualMCPServer.
Technical detail
What's hard-coded today
pkg/vmcp/config.AuthzConfighas Cedar-specific fields baked in:There is no way to represent an HTTP PDP authorizer in this struct.
pkg/vmcp/auth/factory/incoming.go:93hard-codes the Cedar check:No dispatch, no registry, no fallback.
What needs to change
pkg/vmcp/config.AuthzConfig— replace the Cedar-specific fields with a generic backend blob, mirroring howpkg/authz.Configworks for the runner (aTypestring + aRawExtension-style opaque config). The existing Cedar fields can be retained inside a Cedar-specific sub-struct or migrated to the blob representation.pkg/vmcp/auth/factory/incoming.go— replace theif type == "cedar"check with a dispatch through the authorizer registry (pkg/authz/authorizers), so any registered backend can be resolved at runtime.pkg/vmcp/configvalidator — updatevalidateAuthzto accept the new generic shape and validate the backend-specific blob against the registered factory.cmd/thv-operator/pkg/vmcpconfig/converter.go— once the runtime is pluggable,resolveAuthzConfigRefcan resolve any registered backend (remove thecedarv1-only gate). The controller'shandleAuthzConfigcedarv1 check invirtualmcpserver_controller.gocan also be removed.cmd/thv-operator/pkg/vmcpconfigvalidator — align with the new config shape.References
design-mcpauthzconfig-workload-wiring.md(Follow-up section)MCPAuthzConfigCRD and helpers: Add MCPAuthzConfig ref-resolution foundation #5559