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
- Group:
toolhive.stacklok.dev - Version:
v1alpha1 - Kind:
VirtualMCPServer
- Singular:
virtualmcpserver - Plural:
virtualmcpservers - Short Names:
vmcp,virtualmcp
References an existing MCPGroup that defines the backend workloads to aggregate.
Type: GroupRef
Fields:
name(string, required): Name of the MCPGroup resource in the same namespace
Example:
spec:
groupRef:
name: engineering-teamConfigures 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
oidcConfig(OIDCConfigRef, optional): OIDC authentication configuration (required when type=oidc)authzConfig(AuthzConfigRef, optional): Authorization policy configuration
Important: The type field must always be explicitly specified. When no authentication is required, use type: anonymous.
Example (anonymous auth):
spec:
incomingAuth:
type: anonymousExample (OIDC auth):
spec:
incomingAuth:
type: oidc
oidcConfig:
type: kubernetes
kubernetes:
audience: vmcp
authzConfig:
type: inline
inline:
policies:
- |
permit(
principal,
action == Action::"tools/call",
resource
);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: discoveredExample (inline mode):
spec:
outgoingAuth:
source: inline
backends:
github:
type: external_auth_config_ref
externalAuthConfigRef:
name: github-token-exchange
slack:
type: service_account
serviceAccount:
credentialsRef:
name: slack-bot-token
key: token
headerName: Authorization
headerFormat: "Bearer {token}"Fields:
type(string, required): Authentication typediscovered: Automatically discover from backendexternal_auth_config_ref: Reference an MCPExternalAuthConfig resource
externalAuthConfigRef(ExternalAuthConfigRef, optional): Auth config reference (when type=external_auth_config_ref)
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 overrides
Example (prefix strategy):
spec:
aggregation:
conflictResolution: prefix
conflictResolutionConfig:
prefixFormat: "{workload}_"
tools:
- workload: github
filter: ["create_pr", "merge_pr"]
- workload: jira
toolConfigRef:
name: jira-tool-configExample (priority strategy):
spec:
aggregation:
conflictResolution: priority
conflictResolutionConfig:
priorityOrder: ["github", "jira", "slack"]Example (manual strategy):
spec:
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 existFields:
workload(string, required): Name of the backend MCPServer workloadtoolConfigRef(ToolConfigRef, optional): Reference to MCPToolConfig resourcefilter([]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)
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"]Defines operational settings like timeouts and health checks.
Type: OperationalConfig
Fields:
timeouts(TimeoutConfig, optional): Timeout configurationfailureHandling(FailureHandlingConfig, optional): Failure handling configuration
Example:
spec:
operational:
timeouts:
default: 30s
perWorkload:
github: 45s
failureHandling:
healthCheckInterval: 30s
unhealthyThreshold: 3
partialFailureMode: fail
circuitBreaker:
enabled: true
failureThreshold: 5
timeout: 60sDefines 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"Configures OpenTelemetry-based observability for the Virtual MCP server, including distributed tracing, OTLP metrics export, and Prometheus metrics endpoint. Uses the same configuration structure as MCPServer.spec.telemetry.
Type: TelemetryConfig
Fields:
openTelemetry(OpenTelemetryConfig, optional): OpenTelemetry configurationenabled(boolean): Controls whether OpenTelemetry is enabledendpoint(string): OTLP endpoint URL for tracing and metricsserviceName(string): Service name for telemetry (defaults to VirtualMCPServer name)headers([]string): Authentication headers for OTLP endpoint (key=value format)insecure(boolean): Use HTTP instead of HTTPS for the OTLP endpointmetrics(OpenTelemetryMetricsConfig, optional): Metrics-specific configurationenabled(boolean): Controls whether OTLP metrics are sent
tracing(OpenTelemetryTracingConfig, optional): Tracing-specific configurationenabled(boolean): Controls whether OTLP tracing is sentsamplingRate(string): Trace sampling rate (0.0-1.0, default: "0.05")
prometheus(PrometheusConfig, optional): Prometheus-specific configurationenabled(boolean): Controls whether Prometheus metrics endpoint is exposed at /metrics
Example:
spec:
telemetry:
openTelemetry:
enabled: true
endpoint: "otel-collector:4317"
serviceName: "my-vmcp"
insecure: true
tracing:
enabled: true
samplingRate: "0.1"
metrics:
enabled: true
prometheus:
enabled: trueFor details on what metrics and traces are emitted, see the Virtual MCP Server Observability documentation.
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
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
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
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
Provides additional information about the current phase.
Type: string
URL where the Virtual MCP server can be accessed.
Type: string
The most recent generation observed for this VirtualMCPServer.
Type: int64
apiVersion: toolhive.stacklok.dev/v1alpha1
kind: VirtualMCPServer
metadata:
name: engineering-vmcp
namespace: default
spec:
# Reference to MCPGroup defining backend workloads
groupRef:
name: engineering-team
# Client authentication
incomingAuth:
type: oidc
oidcConfig:
type: kubernetes
kubernetes:
audience: 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
# Tool aggregation
aggregation:
conflictResolution: prefix
conflictResolutionConfig:
prefixFormat: "{workload}_"
tools:
- workload: github
filter: ["create_pr", "merge_pr"]
- workload: jira
toolConfigRef:
name: jira-tool-config
# 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
telemetry:
openTelemetry:
enabled: true
endpoint: "otel-collector:4317"
tracing:
enabled: true
samplingRate: "0.1"
metrics:
enabled: true
prometheus:
enabled: true
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: 1The 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
- MCPGroup: Defines groups of MCPServers
- MCPServer: Individual MCP server instances
- MCPExternalAuthConfig: External authentication configuration
- MCPToolConfig: Tool filtering and renaming configuration
- Virtual MCP Server Observability: Telemetry and metrics documentation
- Virtual MCP Proposal: Complete design proposal