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:

TypeCRDInfrastructureUse Case
ContainerMCPServerPod + ServiceMCP servers running as containers in the cluster
ProxyMCPRemoteProxyProxy Pod + ServiceRemote servers requiring a proxy with its own auth/audit layer
EntryMCPServerEntryNone (config only)Remote servers where VirtualMCPServer connects directly

When to use MCPServerEntry vs MCPRemoteProxy:

  • Use MCPServerEntry when 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 MCPRemoteProxy when 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 replay
    • scopes ([]string, optional): Defaults to ["openid"]
  • authzConfig (AuthzConfigRef, optional): Authorization policy configuration
    • type (string, required): inline or configMap
    • inline (InlineAuthzConfig, required when type=inline): Inline Cedar policies
      • policies ([]string, required): Cedar policy strings
      • entitiesJson (string, optional): Cedar entities (JSON). Required when transitive policies (e.g. ClaimGroupPlatformRole) need a static entity store. Defaults to "[]".
      • primaryUpstreamProvider (string, optional): Deprecated. Use .spec.authServerConfig.primaryUpstreamProvider instead. Setting this field still resolves a primary upstream for backward compatibility and emits a Warning event with reason AuthzPrimaryUpstreamProviderDeprecated. 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 vmcp config.yaml. Failures surface as AuthConfigured=False with reason AuthzConfigMapNotFound (missing reference) or AuthzConfigMapInvalid (parse, validation, or non-Cedar payload).
    • groupClaimName (string, optional): JWT claim key Cedar should treat as the group list (overrides the well-known defaults groups, roles, cognito:groups). When type is configMap, the spec value overrides any group_claim_name set in the ConfigMap payload.
    • roleClaimName (string, optional): JWT claim key Cedar should treat as the role list. Same spec-over-ConfigMap precedence as groupClaimName.
    • groupEntityType (string, optional): Cedar entity type used for principal parent UIDs synthesised from JWT group/role claims. Defaults to THVGroup. Must match the entity type used in the static entity store for transitive in checks to resolve. Same spec-over-ConfigMap precedence as groupClaimName.

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 determined
    • discovered (default): Automatically discover from backend's MCPServer.spec.externalAuthConfigRef
    • inline: Explicit per-backend configuration in VirtualMCPServer
  • default (BackendAuthConfig, optional): Default behavior for backends without explicit auth config
  • backends (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 type
    • discovered: Automatically discover from backend
    • externalAuthConfigRef: 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: anonymous only 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 with sessionAffinity: 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 conflicts
    • prefix: Automatically prefix tool names with workload identifier
    • priority: First workload in priority order wins
    • manual: Explicitly define overrides for all conflicts
  • conflictResolutionConfig (ConflictResolutionConfig, optional): Configuration for the chosen strategy
  • tools ([]WorkloadToolConfig, optional): Per-workload tool filtering and overrides
  • excludeAllTools (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 workload
  • toolConfigRef (ToolConfigRef, optional): Reference to MCPToolConfig resource for Kubernetes deployments
  • filter ([]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 tool
  • description (string, required): Description of the composite tool
  • parameters (map[string]ParameterSpec, optional): Input parameters
  • steps ([]WorkflowStep, required): Workflow steps
  • timeout (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 configuration
  • failureHandling (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 metrics
  • serviceName (string): Service name for telemetry
  • serviceVersion (string): Service version for telemetry
  • tracingEnabled (boolean): Controls whether distributed tracing is enabled
  • metricsEnabled (boolean): Controls whether OTLP metrics are enabled
  • samplingRate (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 endpoint
  • insecure (boolean): Use HTTP instead of HTTPS for the OTLP endpoint
  • enablePrometheusMetricsPath (boolean): Controls whether to expose Prometheus-style /metrics endpoint
  • environmentVariables ([]string): Environment variable names to include in telemetry spans as attributes
  • customAttributes (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 ready
  • AuthConfigured: Indicates whether authentication is configured
  • BackendsDiscovered: Indicates whether backends have been discovered
  • GroupRefValidated: Indicates whether the GroupRef is valid

.status.discoveredBackends

Lists discovered backend configurations when source=discovered.

Type: []DiscoveredBackend

Fields:

  • name (string): Name of the backend MCPServer
  • authConfigRef (string): Name of the discovered MCPExternalAuthConfig
  • authType (string): Type of authentication configured
  • status (string): Current status (ready, degraded, unavailable)
  • lastHealthCheck (metav1.Time): Timestamp of the last health check
  • url (string): URL of the backend MCPServer

.status.capabilities

Summarizes aggregated capabilities from all backends.

Type: CapabilitiesSummary

Fields:

  • toolCount (int): Total number of tools exposed
  • resourceCount (int): Total number of resources exposed
  • promptCount (int): Total number of prompts exposed
  • compositeToolCount (int): Number of composite tools defined

.status.phase

Current phase of the VirtualMCPServer.

Type: VirtualMCPServerPhase

Values:

  • Pending: VirtualMCPServer is being initialized
  • Ready: VirtualMCPServer is ready and serving requests
  • Degraded: VirtualMCPServer is running but some backends are unavailable
  • Failed: 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:

  1. Required Fields:
    • spec.groupRef.name must be specified
    • spec.incomingAuth.type must be explicitly specified (use anonymous when no auth is needed)
  2. Reference Validation: All references (groupRef, authConfigRef, toolConfigRef) must be valid
  3. Conflict Resolution: Priority strategy requires priorityOrder configuration
  4. Composite Tools: Must have unique names, valid steps with IDs, and proper dependencies
  5. Token Cache: Redis provider requires valid address configuration
  6. Same-Namespace References: All references must be in the same namespace for security