Bug description
When a VirtualMCPServer references an MCPAuthzConfig that sets multi_valued_claims, the setting is accepted and validated but never reaches the Cedar authorizer. As a result, the claimset_<name> attribute documented in the authz policy reference (Multi-valued claim normalization) is never created, and Cedar policies that use it always deny.
No validation error or warning is emitted, so the misconfiguration is invisible.
Reproduction
Given an MCPAuthzConfig:
apiVersion: toolhive.stacklok.dev/v1beta1
kind: MCPAuthzConfig
metadata:
name: example-authz
spec:
type: cedarv1
config:
multi_valued_claims: ["scope"]
entities_json: "[]"
policies:
- |
permit(
principal,
action == Action::"call_tool",
resource == Tool::"example_get-weather"
) when {
principal has claimset_scope &&
principal.claimset_scope.contains("weather:read")
};
referenced by a VirtualMCPServer via spec.incomingAuth.authzConfigRef, and a token whose claims (JWT or RFC 7662 introspection) include:
{
"active": true,
"sub": "user-123",
"scope": "weather:read"
}
the tool is excluded from tools/list.
The gateway logs show:
Client::"user-123" does not have the attribute `claimset_scope`
Expected behavior
multi_valued_claims should be honored for VirtualMCPServer the same way it is for thv run, MCPServer, and MCPRemoteProxy: principal.claimset_scope should be a Cedar Set (["weather:read"]) and the policy above should permit the call.
If the field is intentionally unsupported for vMCP, the operator should reject it or surface a status condition instead of silently dropping it.
Actual behavior
The field is parsed into cedar.ConfigOptions.MultiValuedClaims, but the vMCP path copies only an allow-list of fields and drops it:
cmd/thv-operator/pkg/vmcpconfig/converter.go, resolveAuthzConfigRef: builds vmcpconfig.AuthzConfig from Policies, EntitiesJSON, GroupClaimName, RoleClaimName, and GroupEntityType only. The ConfigMap branch of convertAuthzConfig has the same omission.
pkg/vmcp/config/config.go: AuthzConfig has no MultiValuedClaims field.
pkg/vmcp/auth/factory/incoming.go: builds cedar.ConfigOptions without MultiValuedClaims, so the authorizer runs with it empty and never adds claimset_* attributes.
Environment (if relevant)
- ToolHive version: vmcp:v0.49.0
Suggested direction
Plumb MultiValuedClaims through the vMCP path the same way GroupClaimName/RoleClaimName are:
- Add
MultiValuedClaims []string to vmcpconfig.AuthzConfig.
- Copy
opts.MultiValuedClaims in resolveAuthzConfigRef and in the ConfigMap branch of convertAuthzConfig.
- Pass it into
cedar.ConfigOptions in pkg/vmcp/auth/factory/incoming.go.
Optionally, update the docs to note which deployment modes support multi_valued_claims until this lands.
Bug description
When a
VirtualMCPServerreferences anMCPAuthzConfigthat setsmulti_valued_claims, the setting is accepted and validated but never reaches the Cedar authorizer. As a result, theclaimset_<name>attribute documented in the authz policy reference (Multi-valued claim normalization) is never created, and Cedar policies that use it always deny.No validation error or warning is emitted, so the misconfiguration is invisible.
Reproduction
Given an
MCPAuthzConfig:referenced by a
VirtualMCPServerviaspec.incomingAuth.authzConfigRef, and a token whose claims (JWT or RFC 7662 introspection) include:{ "active": true, "sub": "user-123", "scope": "weather:read" }the tool is excluded from
tools/list.The gateway logs show:
Expected behavior
multi_valued_claimsshould be honored forVirtualMCPServerthe same way it is forthv run,MCPServer, andMCPRemoteProxy:principal.claimset_scopeshould be a Cedar Set (["weather:read"]) and the policy above should permit the call.If the field is intentionally unsupported for vMCP, the operator should reject it or surface a status condition instead of silently dropping it.
Actual behavior
The field is parsed into
cedar.ConfigOptions.MultiValuedClaims, but the vMCP path copies only an allow-list of fields and drops it:cmd/thv-operator/pkg/vmcpconfig/converter.go,resolveAuthzConfigRef: buildsvmcpconfig.AuthzConfigfromPolicies,EntitiesJSON,GroupClaimName,RoleClaimName, andGroupEntityTypeonly. The ConfigMap branch ofconvertAuthzConfighas the same omission.pkg/vmcp/config/config.go:AuthzConfighas noMultiValuedClaimsfield.pkg/vmcp/auth/factory/incoming.go: buildscedar.ConfigOptionswithoutMultiValuedClaims, so the authorizer runs with it empty and never addsclaimset_*attributes.Environment (if relevant)
Suggested direction
Plumb
MultiValuedClaimsthrough the vMCP path the same wayGroupClaimName/RoleClaimNameare:MultiValuedClaims []stringtovmcpconfig.AuthzConfig.opts.MultiValuedClaimsinresolveAuthzConfigRefand in the ConfigMap branch ofconvertAuthzConfig.cedar.ConfigOptionsinpkg/vmcp/auth/factory/incoming.go.Optionally, update the docs to note which deployment modes support
multi_valued_claimsuntil this lands.