Skip to main content

Quickstart: Virtual MCP Server

In this tutorial, you'll learn how to deploy Virtual MCP Server (vMCP) to aggregate multiple MCP servers into a single endpoint. By the end, you'll have a working deployment that combines tools from multiple backends.

What you'll learn

  • How to create an MCPGroup to organize backend servers
  • How to deploy multiple MCPServers in a group
  • How to create a VirtualMCPServer that aggregates them
  • How tool conflict resolution works
  • How to connect your AI client to the aggregated endpoint

Prerequisites

Before starting this tutorial, make sure you have:

  • A Kubernetes cluster with the ToolHive operator installed (see Quickstart: Kubernetes Operator)
  • kubectl configured to communicate with your cluster
  • An MCP client. Visual Studio Code with GitHub Copilot is used in this tutorial, but any client that supports remote HTTP MCP servers will work.

Step 1: Create an MCPGroup

First, create an MCPGroup to organize your backend MCP servers:

mcpgroup.yaml
apiVersion: toolhive.stacklok.dev/v1beta1
kind: MCPGroup
metadata:
name: demo-tools
namespace: toolhive-system
spec:
description: Demo group for vMCP aggregation

Apply the resource:

kubectl apply -f mcpgroup.yaml

Verify the group was created:

kubectl get mcpgroups -n toolhive-system

Step 2: Deploy backend MCPServers

Deploy two MCP servers that will be aggregated. Both reference the demo-tools group in the groupRef field:

mcpservers.yaml
apiVersion: toolhive.stacklok.dev/v1beta1
kind: MCPServer
metadata:
name: fetch
namespace: toolhive-system
spec:
image: ghcr.io/stackloklabs/gofetch/server
transport: streamable-http
proxyPort: 8080
mcpPort: 8080
groupRef:
name: demo-tools
resources:
limits:
cpu: '100m'
memory: '128Mi'
requests:
cpu: '50m'
memory: '64Mi'
---
apiVersion: toolhive.stacklok.dev/v1beta1
kind: MCPServer
metadata:
name: osv
namespace: toolhive-system
spec:
image: ghcr.io/stackloklabs/osv-mcp/server
transport: streamable-http
proxyPort: 8080
mcpPort: 8080
groupRef:
name: demo-tools
resources:
limits:
cpu: '100m'
memory: '128Mi'
requests:
cpu: '50m'
memory: '64Mi'

Apply the resources:

kubectl apply -f mcpservers.yaml

Wait for both servers to be ready:

kubectl get mcpservers -n toolhive-system -w

You should see both servers with Ready status before continuing.

Step 3: Create a VirtualMCPServer

Create a VirtualMCPServer that aggregates both backends:

virtualmcpserver.yaml
apiVersion: toolhive.stacklok.dev/v1beta1
kind: VirtualMCPServer
metadata:
name: demo-vmcp
namespace: toolhive-system
spec:
# No incoming auth for development (anonymous access)
incomingAuth:
type: anonymous

# Auto-discover auth config from backend MCPServers
outgoingAuth:
source: inline
# No default specified will use anonymous

# Expose as ClusterIP (cluster-internal or exposed via Ingress/Gateway)
serviceType: ClusterIP

# Reference the MCPGroup containing fetch and osv servers
groupRef:
name: demo-tools

config:
# Tool aggregation with prefix strategy to avoid naming conflicts
aggregation:
conflictResolution: prefix
conflictResolutionConfig:
prefixFormat: '{workload}_'

Apply the resource:

kubectl apply -f virtualmcpserver.yaml

Check the status:

kubectl get virtualmcpservers -n toolhive-system

After about 30 seconds, you should see output similar to:

NAME PHASE URL BACKENDS AGE READY
demo-vmcp Ready http://vmcp-demo-vmcp.toolhive-system.svc.cluster.local:4483 2 30s True

Note the port number for step 5.

What's happening?

The operator discovered both MCPServers in the group and configured vMCP to aggregate their tools. With the prefix conflict resolution strategy, all tools are prefixed with the backend name.

Step 4: Verify the aggregation

Check the discovered backends:

kubectl describe virtualmcpserver demo-vmcp -n toolhive-system

Look for the Discovered Backends section in the status, which should show both backends.

Step 5: Connect your client

In a separate terminal, port-forward the vMCP service to your local machine:

kubectl port-forward service/vmcp-demo-vmcp -n toolhive-system 4483:4483

Test the health endpoint:

curl http://localhost:4483/health

You should see {"status":"ok"}.

To have ToolHive manage the vMCP connection and configure your clients for you, add the port-forwarded endpoint as a remote server with the ToolHive CLI:

thv run http://localhost:4483/mcp --name demo-vmcp

This registers the vMCP endpoint as a ToolHive-managed workload, which automatically configures your registered MCP clients to connect to it.

tip

If you're using the ToolHive UI instead, add the same endpoint as a remote MCP server from the app. For more details, see Run MCP servers.

If you haven't set up client configuration yet, run thv client setup to register your MCP clients. See Client configuration for more details.

Step 6: Test the aggregated tools

Try asking your AI assistant questions that use the aggregated tools. Both tools work through the same vMCP endpoint!

Step 7: Clean up

Delete the resources when you're done:

kubectl delete virtualmcpserver demo-vmcp -n toolhive-system
kubectl delete mcpserver fetch osv -n toolhive-system
kubectl delete mcpgroup demo-tools -n toolhive-system

Next steps

Congratulations! You've successfully deployed vMCP and aggregated multiple backends into a single endpoint.

Troubleshooting

VirtualMCPServer stuck in Pending

The vMCP pod won't become Ready until the MCPGroup is in Ready state and at least one backend MCPServer is Ready. Check both:

kubectl get mcpgroups,mcpservers -n toolhive-system

If a backend is stuck, check the operator logs for reconciliation errors:

kubectl logs -n toolhive-system -l app.kubernetes.io/name=toolhive-operator

For a deeper walkthrough of discovery failures and RBAC issues, see Backend discovery troubleshooting.

A backend's tools aren't showing up through the vMCP

First, confirm the backend is actually discovered:

kubectl describe virtualmcpserver demo-vmcp -n toolhive-system

Look for the backend in status.discoveredBackends and check its status and message fields. If the backend is missing or unhealthy, see Backend discovery troubleshooting.

If the backend is healthy but tools still aren't appearing, compare what the vMCP exposes against what the individual backend exposes using the ToolHive CLI. Port-forward each in turn, then list tools:

# Port-forward the vMCP
kubectl port-forward service/vmcp-demo-vmcp -n toolhive-system 4483:4483

# In another terminal, list what the vMCP exposes
thv mcp list tools --server http://localhost:4483/mcp

# Then port-forward the backend directly and list its tools
kubectl port-forward service/mcp-fetch-proxy -n toolhive-system 8080:8080
thv mcp list tools --server http://localhost:8080/mcp

If the backend exposes a tool that the vMCP doesn't, check your tool aggregation configuration for filters that might be excluding it. See Tool aggregation for filter and override rules.