VirtualMCPServer API Reference
June 15, 2026 · View on GitHub
Overview
The VirtualMCPServer CRD enables aggregation of multiple backend MCPServers into a unified virtual endpoint. This allows clients to interact with multiple MCP servers through a single interface, with features like:
- Unified authentication: Single authentication point for clients
- Backend discovery: Automatic discovery of backend authentication configurations
- Tool aggregation: Intelligent conflict resolution when multiple backends expose tools with the same name
- Composite tools: Define workflows that orchestrate calls across multiple backends
- Token caching: Efficient token exchange and caching for improved performance
API Group and Version
- Group:
toolhive.stacklok.dev - Version: `v1beta1`
- Kind:
VirtualMCPServer
Resource Names
- Singular:
virtualmcpserver - Plural:
virtualmcpservers - Short Names:
vmcp,virtualmcp
Spec Fields
.spec.groupRef (required)
References an existing MCPGroup that defines the backend workloads to aggregate.
The referenced MCPGroup must exist in the same namespace.
Type: MCPGroupRef (object with name field)
Example:
spec:
groupRef:
name: engineering-team
Backend Types
A VirtualMCPServer aggregates three types of backends from the referenced MCPGroup:
| Type | CRD | Infrastructure | Use Case |
|---|---|---|---|
| Container | MCPServer | Pod + Service | MCP servers running as containers in the cluster |
| Proxy | MCPRemoteProxy | Proxy Pod + Service | Remote servers requiring a proxy with its own auth/audit layer |
| Entry | MCPServerEntry | None (config only) | Remote servers where VirtualMCPServer connects directly |
When to use MCPServerEntry vs MCPRemoteProxy:
- Use
MCPServerEntrywhen VirtualMCPServer can connect directly to the remote server. This is simpler (zero infrastructure) and eliminates the dual auth boundary problem where both the proxy and vMCP need separate auth configs. - Use
MCPRemoteProxywhen you need the proxy's own authentication middleware, audit logging, or observability for standalone (non-vMCP) access to the remote server.
Example: MCPServerEntry backend
apiVersion: toolhive.stacklok.dev/v1beta1
kind: MCPServerEntry
metadata:
name: context7
spec:
remoteUrl: https://mcp.context7.com/mcp
transport: streamable-http
groupRef:
name: engineering-team
# No externalAuthConfigRef — public endpoint, no auth needed
.spec.incomingAuth (optional)
Configures authentication for clients connecting to the Virtual MCP server. Reuses MCPServer OIDC and authorization patterns.
Type: IncomingAuthConfig
Fields:
type(string, required): Authentication type. Must be explicitly specified.anonymous: No authentication required (use this when no auth is needed)oidc: OIDC/OAuth2 authentication
oidcConfigRef(MCPOIDCConfigReference, optional): Reference to a shared MCPOIDCConfig resource (required when type=oidc).name(string, required): Name of the MCPOIDCConfig resource (same namespace)audience(string, required): Must be unique per server to prevent token replayscopes([]string, optional): Defaults to["openid"]
authzConfig(AuthzConfigRef, optional): Authorization policy configurationtype(string, required):inlineorconfigMapinline(InlineAuthzConfig, required when type=inline): Inline Cedar policiespolicies([]string, required): Cedar policy stringsentitiesJson(string, optional): Cedar entities (JSON). Required when transitive policies (e.g.ClaimGroup→PlatformRole) need a static entity store. Defaults to"[]".primaryUpstreamProvider(string, optional): Deprecated. Use.spec.authServerConfig.primaryUpstreamProviderinstead. Setting this field still resolves a primary upstream for backward compatibility and emits a Warning event with reasonAuthzPrimaryUpstreamProviderDeprecated. Planned removal one release after the deprecation cycle.
configMap(ConfigMapAuthzRef, required when type=configMap): Reference to a ConfigMap holding Cedar policies. The operator resolves the ConfigMap at reconcile time and bakes the policies into the rendered vmcpconfig.yaml. Failures surface asAuthConfigured=Falsewith reasonAuthzConfigMapNotFound(missing reference) orAuthzConfigMapInvalid(parse, validation, or non-Cedar payload).groupClaimName(string, optional): JWT claim key Cedar should treat as the group list (overrides the well-known defaultsgroups,roles,cognito:groups). WhentypeisconfigMap, the spec value overrides anygroup_claim_nameset in the ConfigMap payload.roleClaimName(string, optional): JWT claim key Cedar should treat as the role list. Same spec-over-ConfigMap precedence asgroupClaimName.groupEntityType(string, optional): Cedar entity type used for principal parent UIDs synthesised from JWT group/role claims. Defaults toTHVGroup. Must match the entity type used in the static entity store for transitiveinchecks to resolve. Same spec-over-ConfigMap precedence asgroupClaimName.
Important: The type field must always be explicitly specified. When no authentication is required, use type: anonymous.
Example (anonymous auth):
spec:
incomingAuth:
type: anonymous
Example (OIDC auth with shared MCPOIDCConfig — preferred):
spec:
incomingAuth:
type: oidc
oidcConfigRef:
name: corporate-idp # references an MCPOIDCConfig resource
audience: vmcp-api # unique per server
scopes: ["openid"]
authzConfig:
type: inline
inline:
policies:
- |
permit(
principal,
action == Action::"tools/call",
resource
);
.spec.outgoingAuth (optional)
Configures authentication from Virtual MCP to backend MCPServers.
Type: OutgoingAuthConfig
Fields:
source(string, optional): How backend authentication configurations are determineddiscovered(default): Automatically discover from backend'sMCPServer.spec.externalAuthConfigRefinline: Explicit per-backend configuration in VirtualMCPServer
default(BackendAuthConfig, optional): Default behavior for backends without explicit auth configbackends(map[string]BackendAuthConfig, optional): Per-backend authentication overrides
Example (discovered mode):
spec:
outgoingAuth:
source: discovered
default:
type: discovered
Example (inline mode):
spec:
outgoingAuth:
source: inline
backends:
github:
type: externalAuthConfigRef
externalAuthConfigRef:
name: github-token-exchange
slack:
type: service_account
serviceAccount:
credentialsRef:
name: slack-bot-token
key: token
headerName: Authorization
headerFormat: "Bearer {token}"
BackendAuthConfig
Fields:
type(string, required): Authentication typediscovered: Automatically discover from backendexternalAuthConfigRef: Reference an MCPExternalAuthConfig resource
externalAuthConfigRef(ExternalAuthConfigRef, optional): Auth config reference (when type=externalAuthConfigRef)
.spec.passthroughHeaders (optional)
Allowlist of incoming client request headers forwarded verbatim to every
backend. The value is captured at the auth boundary and injected into the
session's outgoing backend requests. Type: []string. Names are matched
case-insensitively; restricted headers (Host, Authorization,
X-Forwarded-*, hop-by-hop) are rejected at startup. Takes precedence over
spec.config.passthroughHeaders.
spec:
passthroughHeaders:
- x-litellm-api-key # forwarded to all backends; absent on a request → not forwarded
Security: forwarded headers are caller-controlled. Use only behind a trusted upstream (gateway/mesh) that sets or strips them; pair with
incomingAuth: anonymousonly in that case.Horizontal scaling: forwarded headers are not persisted across replicas (only the
(issuer, subject)identity tuple is). A session restored on another replica re-captures them from the next request — keep clients pinned withsessionAffinity: ClientIP.
.spec.config.aggregation (optional)
Defines tool aggregation and conflict resolution strategies.
Type: AggregationConfig
Fields:
conflictResolution(string, optional, default: "prefix"): Strategy for resolving tool name conflictsprefix: Automatically prefix tool names with workload identifierpriority: First workload in priority order winsmanual: Explicitly define overrides for all conflicts
conflictResolutionConfig(ConflictResolutionConfig, optional): Configuration for the chosen strategytools([]WorkloadToolConfig, optional): Per-workload tool filtering and overridesexcludeAllTools(bool, optional): Excludes all tools from aggregation when true
Example (prefix strategy):
spec:
groupRef:
name: my-services
aggregation:
conflictResolution: prefix
conflictResolutionConfig:
prefixFormat: "{workload}_"
tools:
- workload: github
filter: ["create_pr", "merge_pr"]
- workload: jira
toolConfigRef:
name: jira-tool-config
Example (priority strategy):
spec:
groupRef:
name: my-services
aggregation:
conflictResolution: priority
conflictResolutionConfig:
priorityOrder: ["github", "jira", "slack"]
Example (manual strategy):
spec:
groupRef:
name: my-services
aggregation:
conflictResolution: manual
tools:
- workload: github
filter: ["create_pr", "merge_pr", "list_repos"]
overrides:
create_pr:
name: github_create_pr
description: "Create a pull request in GitHub"
- workload: jira
filter: ["create_issue", "update_issue"]
overrides:
create_issue:
name: jira_create_issue
description: "Create an issue in Jira"
# All tool name conflicts must be explicitly resolved via overrides
# Runtime validation ensures no unresolved conflicts exist
WorkloadToolConfig
Fields:
workload(string, required): Name of the backend MCPServer workloadtoolConfigRef(ToolConfigRef, optional): Reference to MCPToolConfig resource for Kubernetes deploymentsfilter([]string, optional): Inline list of tool names to allow (only used if toolConfigRef not specified)overrides(map[string]ToolOverride, optional): Inline tool overrides (only used if toolConfigRef not specified)excludeAll(bool, optional): Excludes all tools from this workload when true
.spec.compositeTools (optional)
Defines inline composite tool workflows. For complex workflows, reference VirtualMCPCompositeToolDefinition resources instead.
Type: []CompositeToolSpec
Fields:
name(string, required): Name of the composite tooldescription(string, required): Description of the composite toolparameters(map[string]ParameterSpec, optional): Input parameterssteps([]WorkflowStep, required): Workflow stepstimeout(string, optional, default: "30m"): Maximum execution time
Example:
spec:
compositeTools:
- name: deploy_and_notify
description: Deploy PR with user confirmation and notification
parameters:
pr_number:
type: integer
required: true
steps:
- id: merge
tool: github.merge_pr
arguments:
pr: "{{.params.pr_number}}"
- id: confirm_deploy
type: elicitation
message: "PR {{.params.pr_number}} merged. Proceed with deployment?"
dependsOn: ["merge"]
- id: deploy
tool: kubernetes.deploy
arguments:
pr: "{{.params.pr_number}}"
dependsOn: ["confirm_deploy"]
.spec.config.operational (optional)
Defines operational settings like timeouts and health checks.
Type: OperationalConfig
Fields:
logLevel(string, optional): Log level for the Virtual MCP server. Set to "debug" to enable debug logging.timeouts(TimeoutConfig, optional): Timeout configurationfailureHandling(FailureHandlingConfig, optional): Failure handling configuration
Example:
spec:
config:
operational:
logLevel: debug
timeouts:
default: 30s
perWorkload:
github: 45s
failureHandling:
healthCheckInterval: 30s
unhealthyThreshold: 3
partialFailureMode: fail
circuitBreaker:
enabled: true
failureThreshold: 5
timeout: 60s
.spec.podTemplateSpec (optional)
Defines the pod template for customizing the Virtual MCP server pod configuration. Use the vmcp container name to modify the Virtual MCP server container.
Type: runtime.RawExtension
Example:
spec:
podTemplateSpec:
spec:
containers:
- name: vmcp
resources:
requests:
memory: "256Mi"
cpu: "500m"
limits:
memory: "512Mi"
cpu: "1000m"
.spec.config.telemetry (optional)
Configures OpenTelemetry-based observability for the Virtual MCP server, including distributed tracing, OTLP metrics export, and Prometheus metrics endpoint.
Type: telemetry.Config
Fields:
endpoint(string): OTLP endpoint URL for tracing and metricsserviceName(string): Service name for telemetryserviceVersion(string): Service version for telemetrytracingEnabled(boolean): Controls whether distributed tracing is enabledmetricsEnabled(boolean): Controls whether OTLP metrics are enabledsamplingRate(string): Trace sampling rate (0.0-1.0), only used when tracingEnabled is true. Example: "0.05" for 5% sampling.headers(map[string]string): Authentication headers for the OTLP endpointinsecure(boolean): Use HTTP instead of HTTPS for the OTLP endpointenablePrometheusMetricsPath(boolean): Controls whether to expose Prometheus-style /metrics endpointenvironmentVariables([]string): Environment variable names to include in telemetry spans as attributescustomAttributes(map[string]string): Custom resource attributes to be added to all telemetry signals
Example:
spec:
groupRef:
name: my-group
config:
telemetry:
endpoint: "otel-collector:4317"
serviceName: "my-vmcp"
insecure: true
tracingEnabled: true
samplingRate: "0.1"
metricsEnabled: true
enablePrometheusMetricsPath: true
For details on what metrics and traces are emitted, see the Virtual MCP Server Observability documentation.
Status Fields
.status.conditions
Standard Kubernetes conditions representing the latest observations of the VirtualMCPServer's state.
Type: []metav1.Condition
Standard Condition Types:
Ready: Indicates whether the VirtualMCPServer is readyAuthConfigured: Indicates whether authentication is configuredBackendsDiscovered: Indicates whether backends have been discoveredGroupRefValidated: Indicates whether the GroupRef is valid
.status.discoveredBackends
Lists discovered backend configurations when source=discovered.
Type: []DiscoveredBackend
Fields:
name(string): Name of the backend MCPServerauthConfigRef(string): Name of the discovered MCPExternalAuthConfigauthType(string): Type of authentication configuredstatus(string): Current status (ready,degraded,unavailable)lastHealthCheck(metav1.Time): Timestamp of the last health checkurl(string): URL of the backend MCPServer
.status.capabilities
Summarizes aggregated capabilities from all backends.
Type: CapabilitiesSummary
Fields:
toolCount(int): Total number of tools exposedresourceCount(int): Total number of resources exposedpromptCount(int): Total number of prompts exposedcompositeToolCount(int): Number of composite tools defined
.status.phase
Current phase of the VirtualMCPServer.
Type: VirtualMCPServerPhase
Values:
Pending: VirtualMCPServer is being initializedReady: VirtualMCPServer is ready and serving requestsDegraded: VirtualMCPServer is running but some backends are unavailableFailed: VirtualMCPServer has failed
.status.message
Provides additional information about the current phase.
Type: string
.status.url
URL where the Virtual MCP server can be accessed.
Type: string
.status.oidcConfigHash
Hash of the referenced MCPOIDCConfig spec, used for change detection. Only present when oidcConfigRef is set.
Type: string
.status.observedGeneration
The most recent generation observed for this VirtualMCPServer.
Type: int64
Complete Example
apiVersion: toolhive.stacklok.dev/v1beta1
kind: VirtualMCPServer
metadata:
name: engineering-vmcp
namespace: default
spec:
# Reference to MCPGroup defining backend workloads
groupRef:
name: engineering-team
# Tool aggregation
config:
aggregation:
conflictResolution: prefix
conflictResolutionConfig:
prefixFormat: "{workload}_"
tools:
- workload: github
filter: ["create_pr", "merge_pr"]
- workload: jira
toolConfigRef:
name: jira-tool-config
# Client authentication (preferred: reference a shared MCPOIDCConfig)
incomingAuth:
type: oidc
oidcConfigRef:
name: engineering-idp # references an MCPOIDCConfig in the same namespace
audience: engineering-vmcp
authzConfig:
type: inline
inline:
policies:
- |
permit(
principal,
action == Action::"tools/call",
resource
);
# Backend authentication (discovered mode)
outgoingAuth:
source: discovered
default:
type: discovered
backends:
slack: # Override for specific backend
type: service_account
serviceAccount:
credentialsRef:
name: slack-bot-token
key: token
# Composite tools
compositeTools:
- name: investigate_incident
description: Gather logs and metrics for incident analysis
parameters:
incident_id:
type: string
required: true
steps:
- id: fetch_logs
tool: fetch.fetch
arguments:
url: "https://logs.company.com/api/query?incident={{.params.incident_id}}"
- id: create_report
tool: jira.create_issue
arguments:
title: "Incident {{.params.incident_id}} Analysis"
description: "{{.steps.fetch_logs.output}}"
dependsOn: ["fetch_logs"]
# Operational settings
operational:
timeouts:
default: 30s
perWorkload:
github: 45s
failureHandling:
healthCheckInterval: 30s
unhealthyThreshold: 3
partialFailureMode: fail
circuitBreaker:
enabled: true
failureThreshold: 5
timeout: 60s
# Observability is configured in spec.config.telemetry (see .spec.config.telemetry section above)
status:
phase: Ready
message: "Virtual MCP serving 3 backends with 15 tools"
url: "http://engineering-vmcp.default.svc.cluster.local:8080"
observedGeneration: 1
conditions:
- type: Ready
status: "True"
lastTransitionTime: "2025-10-20T10:00:00Z"
reason: AllBackendsReady
message: "Virtual MCP is ready and serving requests"
- type: AuthConfigured
status: "True"
reason: IncomingAuthValid
message: "Incoming authentication configured"
- type: BackendsDiscovered
status: "True"
reason: DiscoveryComplete
message: "Discovered 3 backends with authentication"
discoveredBackends:
- name: github
authConfigRef: github-token-exchange
authType: token_exchange
status: ready
lastHealthCheck: "2025-10-20T10:05:00Z"
url: "http://github-mcp.default.svc.cluster.local:8080"
- name: jira
authConfigRef: jira-token-exchange
authType: token_exchange
status: ready
lastHealthCheck: "2025-10-20T10:05:00Z"
url: "http://jira-mcp.default.svc.cluster.local:8080"
- name: slack
authConfigRef: ""
authType: service_account
status: ready
lastHealthCheck: "2025-10-20T10:05:00Z"
url: "http://slack-mcp.default.svc.cluster.local:8080"
capabilities:
toolCount: 15
resourceCount: 3
promptCount: 2
compositeToolCount: 1
Validation
The VirtualMCPServer CRD includes comprehensive validation:
- Required Fields:
spec.groupRef.namemust be specifiedspec.incomingAuth.typemust be explicitly specified (useanonymouswhen no auth is needed)
- Reference Validation: All references (groupRef, authConfigRef, toolConfigRef) must be valid
- Conflict Resolution: Priority strategy requires
priorityOrderconfiguration - Composite Tools: Must have unique names, valid steps with IDs, and proper dependencies
- Token Cache: Redis provider requires valid address configuration
- Same-Namespace References: All references must be in the same namespace for security
Related Resources
- MCPGroup: Defines groups of MCPServers
- MCPServer: Individual MCP server instances
- MCPOIDCConfig: Shared OIDC provider configuration (referenced via
oidcConfigRef) - MCPExternalAuthConfig: External authentication configuration
- MCPToolConfig: Tool filtering and renaming configuration
- Virtual MCP Server Observability: Telemetry and metrics documentation
- Virtual MCP Proposal: Complete design proposal