ToolHive Kubernetes Operator
April 28, 2026 ยท View on GitHub
The ToolHive Kubernetes Operator manages MCP (Model Context Protocol) servers and registries in Kubernetes clusters. It allows you to define MCP servers and registries as Kubernetes resources and automates their deployment and management.
This operator is built using Kubebuilder, a framework for building Kubernetes APIs using Custom Resource Definitions (CRDs).
This guide is intended for developers working on the ToolHive Operator. For user-facing documentation, please refer to the ToolHive docs website.
Overview
The operator introduces two main Custom Resource Definitions (CRDs):
MCPServer
Represents an MCP server in Kubernetes. When you create an MCPServer resource, the operator automatically:
- Creates a Deployment to run the MCP server
- Sets up a Service to expose the MCP server
- Configures the appropriate permissions and settings
- Manages the lifecycle of the MCP server
MCPRegistry
Represents an MCP server registry in Kubernetes. When you create an MCPRegistry resource, the operator automatically:
- Synchronizes registry data from various sources (ConfigMap, Git)
- Deploys a Registry API service for server discovery
- Manages automatic and manual synchronization policies
For detailed MCPRegistry documentation, see REGISTRY.md.
---
config:
theme: dark
look: classic
layout: dagre
---
flowchart LR
subgraph Kubernetes
direction LR
namespace
User1["Client"]
end
subgraph namespace[namespace: toolhive-system]
operator["POD: Operator"]
sse
streamable-http
stdio
end
subgraph sse[SSE MCP Server Components]
operator -- creates --> THVProxySSE[POD: ToolHive-Proxy] & TPSSSE[SVC: ToolHive-Proxy]
THVProxySSE -- creates --> MCPServerSSE[POD: MCPServer] & MCPHeadlessSSE[SVC: MCPServer-HeadlessService]
User1 -- HTTP/SSE --> TPSSSE
TPSSSE -- HTTP/SSE --> THVProxySSE
THVProxySSE -- HTTP/SSE --> MCPHeadlessSSE
MCPHeadlessSSE -- HTTP/SSE --> MCPServerSSE
end
subgraph stdio[STDIO MCP Server Components]
operator -- creates --> THVProxySTDIO[POD: ToolHive-Proxy] & TPSSTDIO[SVC: ToolHive-Proxy]
THVProxySTDIO -- creates --> MCPServerSTDIO[POD: MCPServer]
User1 -- HTTP/SSE --> TPSSTDIO
TPSSTDIO -- HTTP/SSE --> THVProxySTDIO
THVProxySTDIO -- Attaches/STDIO --> MCPServerSTDIO
end
Installation
Running Operator Unit & Integration Tests
To run the basic operator-only tests (unit and integration), use the following command from the root of the project:
task operator:operator-test
This will run all Go tests in the operator codebase.
Running Operator E2E Tests
The task commands for the operator are designed to be run from the root of the project.
E2E Test Prerequisites
To run the Operator end-to-end (E2E) tests locally, ensure you have the following installed:
- Go
- Kind
- Kind Load Balancer
- Task
- Chainsaw (automatically installed by the Taskfile for local runs)
Steps
- Set up the Kind cluster:
task operator:kind-setup
- Run the Operator E2E tests:
task operator:operator-e2e-test
Note: The Taskfile will ensure Chainsaw is installed locally if not present. In CI, Chainsaw is installed via the GitHub Action.
Prerequisites
- Kubernetes cluster (v1.19+)
- kubectl configured to communicate with your cluster
Installing the Operator via Helm
- Install the CRD:
helm upgrade -i toolhive-operator-crds oci://ghcr.io/stacklok/toolhive/toolhive-operator-crds
- Install the operator:
# Standard installation
helm upgrade -i <release_name> oci://ghcr.io/stacklok/toolhive/toolhive-operator --version=<version> -n toolhive-system --create-namespace
Usage
Creating an MCP Server
To create an MCP server, define an MCPServer resource and apply it to your cluster:
apiVersion: toolhive.stacklok.dev/v1beta1
kind: MCPServer
metadata:
name: fetch
spec:
image: docker.io/mcp/fetch
transport: stdio
proxyPort: 8080
mcpPort: 8080
resources:
limits:
cpu: '100m'
memory: '128Mi'
requests:
cpu: '50m'
memory: '64Mi'
Apply this resource:
kubectl apply -f your-mcpserver.yaml
Using Secrets
For MCP servers that require authentication tokens or other secrets:
apiVersion: toolhive.stacklok.dev/v1beta1
kind: MCPServer
metadata:
name: github
namespace: toolhive-system
spec:
image: ghcr.io/github/github-mcp-server
proxyPort: 8080
mcpPort: 8080
secrets:
- name: github-token
key: token
targetEnvName: GITHUB_PERSONAL_ACCESS_TOKEN
First, create the secret:
kubectl create secret generic github-token -n toolhive-system --from-literal=token=<YOUR_GITHUB_TOKEN>
Then apply the MCPServer resource.
The secrets field has the following parameters:
name: The name of the Kubernetes secret (required)key: The key in the secret itself (required)targetEnvName: The environment variable to be used when setting up the secret in the MCP server (optional). If left unspecified, it defaults to the key.
Checking MCP Server Status
To check the status of your MCP servers:
kubectl get mcpservers
This will show the status, URL, and age of each MCP server.
For more details about a specific MCP server:
kubectl describe mcpserver <name>
Configuration Reference
MCPServer Spec
| Field | Description | Required | Default |
|---|---|---|---|
image | Container image for the MCP server | Yes | - |
transport | Transport method (stdio, streamable-http or sse) | No | stdio |
proxyPort | Port to expose the MCP server on | No | 8080 |
mcpPort | Port that MCP server listens to | No | - |
args | Additional arguments to pass to the MCP server | No | - |
env | Environment variables to set in the container | No | - |
volumes | Volumes to mount in the container | No | - |
resources | Resource requirements for the container | No | - |
secrets | References to secrets to mount in the container | No | - |
permissionProfile | Permission profile configuration (not implemented) | No | - |
tools | Allow-list filter on the list of tools | No | - |
Creating an MCP Registry
The MCPRegistry CRD uses a configYAML field that contains the complete
registry server configuration. The operator passes this content through
to the registry server verbatim.
First, create a ConfigMap containing ToolHive registry data. The ConfigMap must be user-defined and is not managed by the operator:
# Create ConfigMap from existing registry data
kubectl create configmap my-registry-data --from-file registry.json=pkg/registry/data/registry.json -n toolhive-system
# Or create from your own registry file
kubectl create configmap my-registry-data --from-file registry.json=/path/to/your/registry.json -n toolhive-system
Then create the MCPRegistry resource with configYAML and mount the ConfigMap:
apiVersion: toolhive.stacklok.dev/v1beta1
kind: MCPRegistry
metadata:
name: my-registry
namespace: toolhive-system
spec:
displayName: 'My MCP Registry'
configYAML: |
sources:
- name: my-source
file:
path: /config/registry/my-source/registry.json
syncPolicy:
interval: 1h
registries:
- name: default
sources: ["my-source"]
database:
host: registry-db-rw
port: 5432
user: db_app
database: registry
auth:
mode: anonymous
volumes:
- name: my-source-data
configMap:
name: my-registry-data
items:
- key: registry.json
path: registry.json
volumeMounts:
- name: my-source-data
mountPath: /config/registry/my-source
readOnly: true
For complete MCPRegistry examples and documentation, see REGISTRY.md and the examples/operator/mcp-registries/ directory.
Examples
- MCPServer examples:
examples/operator/mcp-servers/directory - MCPRegistry examples:
examples/operator/mcp-registries/directory
Development
Building the Operator
To build the operator:
go build -o bin/thv-operator cmd/thv-operator/main.go
Running Locally
For development, you can run the operator locally:
go run cmd/thv-operator/main.go
This will use your current kubeconfig to connect to the cluster.
Using Kubebuilder
This operator is scaffolded using Kubebuilder. If you want to make changes to the API or controller, you can use Kubebuilder commands to help you.
Prerequisites
- Install Kubebuilder: https://book.kubebuilder.io/quick-start.html#installation
Common Commands
Generate CRD manifests:
kubebuilder create api --group toolhive --version v1beta1 --kind MCPServer
Update CRD manifests after changing API types:
task operator-manifests
Run the controller locally:
task operator-run
Project Structure
The Kubebuilder project structure is as follows:
api/v1beta1/: Contains the API definitions for the CRDscontrollers/: Contains the reconciliation logic for the controllersconfig/: Contains the Kubernetes manifests for deploying the operatorPROJECT: Kubebuilder project configuration file
For more information on Kubebuilder, see the Kubebuilder Book.