A RetroSearch Logo

Home - News ( United States | United Kingdom | Italy | Germany ) - Football scores

Search Query:

Showing content from https://docs.stacklok.com/toolhive/guides-k8s/run-mcp-k8s below:

Run MCP servers in Kubernetes

Run MCP servers in Kubernetes Prerequisites Overview

The ToolHive operator deploys MCP servers in Kubernetes by creating proxy pods that manage the actual MCP server containers. Here's how the architecture works:

High-level architecture

This diagram shows the basic relationship between components. The ToolHive operator watches for MCPServer resources and automatically creates the necessary infrastructure to run your MCP servers securely within the cluster.

STDIO transport flow

For MCP servers using STDIO transport, the proxy directly attaches to the MCP server pod's standard input/output streams.

Streamable HTTP and SSE transport flow

For MCP servers using Server-Sent Events (SSE) or Stremable HTTP transport, the proxy creates both a pod and a headless service. This allows direct HTTP/SSE or HTTP/Streamable HTTP communication between the proxy and MCP server while maintaining network isolation and service discovery.

Create an MCP server

You can create MCPServer resources in namespaces based on how the operator was deployed.

See Deploy the operator to learn about the different deployment modes.

To create an MCP server, define an MCPServer resource and apply it to your cluster. This minimal example creates the osv MCP server which queries the Open Source Vulnerability (OSV) database for vulnerability information.

my-mcpserver.yaml

apiVersion: toolhive.stacklok.dev/v1alpha1
kind: MCPServer
metadata:
name: osv
namespace: my-namespace
spec:
image: ghcr.io/stackloklabs/osv-mcp/server
transport: streamable-http
port: 8080
permissionProfile:
type: builtin
name: network
resources:
limits:
cpu: '100m'
memory: '128Mi'
requests:
cpu: '50m'
memory: '64Mi'

Apply the resource:

kubectl apply -f my-mcpserver.yaml

What's happening?

When you apply an MCPServer resource, here's what happens:

  1. The ToolHive operator detects the new resource (if it's in an allowed namespace)
  2. The operator automatically creates the necessary RBAC resources in the target namespace:
  3. The operator creates a new Deployment containing a ToolHive proxy pod and service to handle client connections
  4. The proxy creates the actual MCPServer pod containing your specified container image
  5. For STDIO transport, the proxy attaches directly to the pod; for SSE and Streamable HTTP transport, a headless service is created for direct pod communication
  6. Clients can now connect through the service → proxy → MCP server chain to use the tools and resources (note: external clients will need an ingress controller or similar mechanism to access the service from outside the cluster)

For more examples of MCPServer resources, see the example MCP server manifests in the ToolHive repo.

Automatic RBAC management

The ToolHive operator automatically handles RBAC (Role-Based Access Control) for each MCPServer instance, providing better security isolation and multi-tenant support. Here's what the operator creates automatically:

This approach provides:

Customize server settings

You can customize the MCP server by adding additional fields to the MCPServer resource. The full specification is available in the Kubernetes CRD reference.

Below are some common configurations.

Customize the MCP server pod

You can customize the MCP server pod that gets created by the proxy using the podTemplateSpec field. This gives you full control over the pod specification, letting you set security contexts, resource limits, node selectors, and other pod-level configurations.

The podTemplateSpec field follows the standard Kubernetes PodTemplateSpec format, so you can use any valid pod specification options.

This example sets resource limits.

my-mcpserver-custom-pod.yaml

apiVersion: toolhive.stacklok.dev/v1alpha1
kind: MCPServer
metadata:
name: fetch
namespace: development
spec:
image: ghcr.io/stackloklabs/gofetch/server
transport: streamable-http
port: 8080
permissionProfile:
type: builtin
name: network
podTemplateSpec:
spec:
containers:
- name: mcp
resources:
limits:
cpu: '500m'
memory: '512Mi'
requests:
cpu: '100m'
memory: '128Mi'
resources:
limits:
cpu: '100m'
memory: '128Mi'
requests:
cpu: '50m'
memory: '64Mi'

Container name requirement

When customizing containers in podTemplateSpec, you must use name: mcp for the main container. This ensures the proxy can properly manage the MCP server process.

Run a server with secrets

For MCP servers that require authentication tokens or other secrets, add the secrets field to the MCPServer resource. This example shows how to use a Kubernetes secret to pass a GitHub personal access token to the github MCP server.

my-mcpserver-with-secrets.yaml

apiVersion: toolhive.stacklok.dev/v1alpha1
kind: MCPServer
metadata:
name: github
namespace: production
spec:
image: ghcr.io/github/github-mcp-server
transport: stdio
port: 8080
permissionProfile:
type: builtin
name: network
secrets:
- name: github-token
key: token
targetEnvName: GITHUB_PERSONAL_ACCESS_TOKEN

First, create the secret. Note that the secret must be created in the same namespace as the MCP server and the key must match the one specified in the MCPServer resource.

kubectl -n production create secret generic github-token --from-literal=token=<YOUR_TOKEN>

Apply the MCPServer resource:

kubectl apply -f my-mcpserver-with-secrets.yaml
Mount a volume

You can mount volumes into the MCP server pod to provide persistent storage or access to data. This is useful for MCP servers that need to read/write files or access large datasets.

To do this, add a standard volumes field to the podTemplateSpec in the MCPServer resource and a volumeMounts section in the container specification. Here's an example that mounts a persistent volume claim (PVC) to the /projects path in the Filesystem MCP server. The PVC must already exist in the same namespace as the MCPServer.

my-mcpserver-with-volume.yaml

apiVersion: toolhive.stacklok.dev/v1alpha1
kind: MCPServer
metadata:
name: filesystem
namespace: data-processing
spec:
image: docker.io/mcp/filesystem
transport: stdio
port: 8080
permissionProfile:
type: builtin
name: none
podTemplateSpec:
spec:
volumes:
- name: my-mcp-data
persistentVolumeClaim:
claimName: my-mcp-data-claim
containers:
- name: mcp

volumeMounts:
- mountPath: /projects/my-mcp-data
name: my-mcp-data
readOnly: true
Check MCP server status

To check the status of your MCP servers in a specific namespace:

kubectl -n <NAMESPACE> get mcpservers

To check MCP servers across all namespaces:

kubectl get mcpservers --all-namespaces

The status, URL, and age of each MCP server is displayed.

For more details about a specific MCP server:

kubectl -n <NAMESPACE> describe mcpserver <NAME>
Next steps

See the Client compatibility reference to learn how to connect to MCP servers using different clients.

important

Outbound network filtering using permission profiles isn't currently implemented in the ToolHive Operator. This is a roadmap feature planned for future releases.

Contributions to help implement this feature are welcome! You can contribute by visiting our GitHub repository.

Troubleshooting MCPServer resource not creating pods

If your MCPServer resource is created but no pods appear, first ensure you created the MCPServer resource in an allowed namespace. If the operator runs in namespace mode and you didn't include the namespace in the allowedNamespaces list, the operator ignores the resource. Check the operator's configuration:

helm get values toolhive-operator -n toolhive-system

Check the operator.rbac.scope and operator.rbac.allowedNamespaces properties. If the operator runs in namespace mode, add the namespace where you created the MCPServer to the allowedNamespaces list. See Operator deployment modes.

If the operator runs in cluster mode (default) or the MCPServer is in an allowed namespace, check the operator logs and resource status:


kubectl -n <NAMESPACE> describe mcpserver <NAME>


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


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

Other common causes include:

MCP server pod fails to start

If the MCP server pod is created but fails to start or is in CrashLoopBackOff:


kubectl -n <NAMESPACE> get pods


kubectl -n <NAMESPACE> describe pod <POD_NAME>


kubectl -n <NAMESPACE> logs <POD_NAME> -c mcp

Common causes include:

Proxy pod connection issues

If the proxy pod is running but clients cannot connect:


kubectl -n <NAMESPACE> get pods -l app.kubernetes.io/instance=<MCPSERVER_NAME>


kubectl -n <NAMESPACE> logs -l app.kubernetes.io/instance=<MCPSERVER_NAME>


kubectl -n <NAMESPACE> get services

Common causes include:

Secret mounting issues

If secrets are not being properly mounted or environment variables are missing:


kubectl -n <NAMESPACE> get secret <SECRET_NAME>


kubectl -n <NAMESPACE> describe secret <SECRET_NAME>


kubectl -n <NAMESPACE> exec <POD_NAME> -c mcp -- env | grep <ENV_VAR_NAME>

Common causes include:

Volume mounting problems

If persistent volumes or other volumes are not mounting correctly:


kubectl -n <NAMESPACE> get pvc


kubectl -n <NAMESPACE> describe pvc <PVC_NAME>


kubectl -n <NAMESPACE> describe pod <POD_NAME>

Common causes include:

Permission profile errors

If the MCP server fails due to permission profile issues:


kubectl -n <NAMESPACE> get configmap <CONFIGMAP_NAME>


kubectl -n <NAMESPACE> describe configmap <CONFIGMAP_NAME>


kubectl -n toolhive-system logs -l app.kubernetes.io/name=toolhive-operator | grep -i permission

Common causes include:

Resource limit issues

If pods are being killed due to resource constraints:


kubectl -n <NAMESPACE> top pods


kubectl -n <NAMESPACE> get events --sort-by='.lastTimestamp'


kubectl -n <NAMESPACE> describe pod <POD_NAME>

Solutions:

Debugging connectivity

To test connectivity between components:


kubectl -n <NAMESPACE> port-forward service/<MCPSERVER_NAME> 8080:8080


curl http://localhost:8080/health


kubectl -n <NAMESPACE> get endpoints
Getting more debug information

For additional debugging information:


kubectl -n <NAMESPACE> get all -l app.kubernetes.io/instance=<MCPSERVER_NAME>


kubectl -n <NAMESPACE> get events --field-selector involvedObject.kind=MCPServer


kubectl -n <NAMESPACE> get mcpserver <NAME> -o yaml

RetroSearch is an open source project built by @garambo | Open a GitHub Issue

Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo

HTML: 3.2 | Encoding: UTF-8 | Version: 0.7.4