The registry system is one of ToolHive's key innovations - providing a curated catalog of trusted MCP servers with metadata, configuration, and provenance information. This document explains how registries work, how to use them, and how to host your own.
ToolHive was early to adopt the concept of an MCP server registry. The registry provides:
- Curated catalog of trusted MCP servers
- Metadata including tools, permissions, and configuration
- Provenance information for supply chain security
- Easy deployment - just reference by name
- Custom registries for organizations
graph TB
subgraph "Registry Sources"
Builtin[Built-in Registry<br/>Embedded JSON]
Git[Git Repository]
CM[ConfigMap]
end
subgraph "ToolHive CLI"
CLI[thv CLI]
Provider[Provider Interface<br/>Local/Remote]
end
subgraph "Kubernetes"
MCPReg[MCPRegistry CRD]
Operator[thv-operator]
API[Registry API Service<br/>Optional]
end
Builtin --> Provider
Git --> MCPReg
CM --> MCPReg
Provider --> CLI
MCPReg --> Operator
Operator --> API
style Builtin fill:#81c784
style Git fill:#90caf9
style CM fill:#90caf9
ToolHive ships with a curated registry from toolhive-registry.
Features:
- Maintained by Stacklok
- Trusted and verified servers
- Provenance information
- Regular updates
Browse registry:
thv registry list
thv search <query>Run from registry:
thv run server-nameImplementation:
- Embedded:
pkg/registry/data/registry.json - Manager:
pkg/registry/provider.go,pkg/registry/provider_local.go,pkg/registry/provider_remote.go - Update:
cmd/regup/(registry updater tool)
Implementation: pkg/registry/types.go
{
"version": "1.0.0",
"last_updated": "2025-10-13T12:00:00Z",
"servers": {
"server-name": { /* ImageMetadata */ }
},
"remote_servers": {
"remote-name": { /* RemoteServerMetadata */ }
},
"groups": [
{ /* Group */ }
]
}Implementation: pkg/registry/types.go
{
"name": "weather-server",
"description": "Provides weather information for locations",
"tier": "Official",
"status": "active",
"image": "ghcr.io/stacklok/mcp-weather:v1.0.0",
"transport": "sse",
"target_port": 3000,
"tools": ["get-weather", "get-forecast"],
"permissions": {
"network": {
"outbound": {
"allow_host": ["api.weather.gov"],
"allow_port": [443]
}
}
},
"env_vars": [
{
"name": "API_KEY",
"description": "Weather API key",
"required": true,
"secret": true
}
],
"args": ["--port", "3000"],
"docker_tags": ["v1.0.0", "latest"],
"metadata": {
"stars": 150,
"pulls": 5000,
"last_updated": "2025-10-01T10:00:00Z"
},
"repository_url": "https://github.com/example/weather-mcp",
"tags": ["weather", "api", "official"],
"provenance": {
"sigstore_url": "https://rekor.sigstore.dev",
"repository_uri": "https://github.com/example/weather-mcp",
"signer_identity": "build@example.com",
"runner_environment": "github-actions",
"cert_issuer": "https://token.actions.githubusercontent.com"
}
}Implementation: pkg/registry/types.go
{
"name": "cloud-mcp-server",
"description": "Cloud-hosted MCP server",
"tier": "Partner",
"status": "active",
"url": "https://mcp.example.com/sse",
"transport": "sse",
"tools": ["data-analysis", "ml-inference"],
"headers": [
{
"name": "X-API-Key",
"description": "API key for authentication",
"required": true,
"secret": true
}
],
"env_vars": [
{
"name": "REGION",
"description": "Cloud region",
"required": false,
"default": "us-east-1"
}
],
"metadata": {
"stars": 200,
"last_updated": "2025-10-10T15:00:00Z"
},
"repository_url": "https://github.com/example/cloud-mcp",
"tags": ["cloud", "ml", "partner"]
}Implementation: pkg/registry/types.go
{
"name": "data-pipeline",
"description": "Data processing pipeline tools",
"servers": {
"data-ingestion": { /* ImageMetadata */ },
"data-transform": { /* ImageMetadata */ }
},
"remote_servers": {
"data-storage": { /* RemoteServerMetadata */ }
}
}List all servers:
thv registry listSearch by keyword:
thv search weatherShow server details:
thv registry info weather-serverImplementation: cmd/thv/app/registry.go, cmd/thv/app/search.go
Simple run:
thv run weather-serverWhat happens:
- Look up
weather-serverin registry - Get image, transport, permissions from metadata
- Prompt for required env vars
- Create RunConfig with registry defaults
- Deploy workload
With overrides:
thv run weather-server \
--env API_KEY=xyz \
--proxy-port 9000 \
--permission-profile custom.jsonUser overrides take precedence over registry defaults.
Implementation: cmd/thv/app/run.go
Registry defines requirements:
{
"env_vars": [
{
"name": "API_KEY",
"description": "Weather API key from weather.gov",
"required": true,
"secret": true
},
{
"name": "CACHE_TTL",
"description": "Cache TTL in seconds",
"required": false,
"default": "3600"
}
]
}ToolHive handles:
- Prompts for required variables if not provided
- Uses defaults for optional variables
- Stores secrets securely
- Adds to RunConfig
Implementation: pkg/registry/types.go
Organizations can provide their own registries.
Create registry JSON:
{
"version": "1.0.0",
"servers": {
"internal-tool": {
"name": "internal-tool",
"image": "registry.company.com/mcp/internal-tool:latest",
"transport": "stdio",
"permissions": { "network": { "outbound": { "insecure_allow_all": true }}}
}
}
}Add to ToolHive:
Custom registries can be configured in the ToolHive configuration file.
Configuration location:
- Linux:
~/.config/toolhive/config.yaml - macOS:
~/Library/Application Support/toolhive/config.yaml
Implementation: pkg/config/
Remote registries can be configured in the ToolHive configuration file to fetch registry data from external sources.
ToolHive fetches:
- On startup
- Caches locally
Authentication:
- Basic auth:
https://user:pass@registry.company.com/registry.json - Bearer token: via environment variable
Implementation: pkg/registry/provider.go, pkg/registry/provider_local.go, pkg/registry/provider_remote.go, pkg/registry/factory.go
ToolHive supports live MCP Registry API endpoints that implement the official MCP Registry API v0.1 specification. This enables on-demand querying of servers from dynamic registry APIs.
Key differences from Remote Registry:
- On-demand queries: Fetches servers as needed, not bulk download
- Live data: Always queries the latest data from the API
- Standard protocol: Uses official MCP Registry API specification
- Pagination support: Handles large registries via cursor-based pagination
- Search capabilities: Supports server search via API queries
Set API registry:
thv config set-registry-api https://registry.example.comWith private IP support:
thv config set-registry-api https://registry.internal.company.com --allow-private-ipCheck current registry:
thv config get-registry
# Output: Current registry: https://registry.example.com (API endpoint)Unset API registry:
thv config unset-registryAPI Requirements:
The API endpoint must implement:
GET /v0.1/servers- List all servers with paginationGET /v0.1/servers/:name- Get specific server by reverse-DNS nameGET /v0.1/servers?search=<query>- Search serversGET /openapi.yaml- OpenAPI specification (version 1.0.0)
Response format:
Servers are returned in the upstream MCP Registry format:
{
"server": {
"name": "io.github.example/weather",
"description": "Weather information MCP server",
"packages": [
{
"registry_type": "oci",
"identifier": "ghcr.io/example/weather-mcp:v1.0.0",
"version": "v1.0.0"
}
],
"remotes": [],
"repository": {
"type": "git",
"url": "https://github.com/example/weather-mcp"
}
}
}Type conversion:
ToolHive automatically converts upstream MCP Registry types to internal format:
- Container servers:
packageswithregistry_type: "oci"→ImageMetadata - Remote servers:
remoteswith SSE/HTTP transport →RemoteServerMetadata - Package formats:
oci/docker→ Docker image referencenpm→npx://<package>@<version>pypi→uvx://<package>@<version>
Implementation:
pkg/registry/api/client.go- MCP Registry API clientpkg/registry/provider_api.go- API provider implementation with type conversionpkg/config/registry.go- Configuration methods (setRegistryAPI)pkg/registry/factory.go- Provider factory with API supportcmd/thv/app/config.go- CLI commands
Use cases:
- Connect to official MCP Registry at https://registry.modelcontextprotocol.io
- Point to organization's private MCP Registry API
- Use third-party registry services
- Dynamic server catalogs that update frequently
When multiple registries configured, ToolHive uses this priority order:
- API Registry (if configured) - Highest priority for live data
- Remote Registry (if configured) - Static remote registry URL
- Local Registry (if configured) - Custom local file
- Built-in Registry - Default embedded registry
The factory selects the first configured registry type in this order. To switch between registry types, use the appropriate CLI command:
# Set API registry (highest priority, for example, https://registry.modelcontextprotocol.io)
thv config set-registry-api https://registry.example.com
# Set remote registry (if no API registry configured)
thv config set-registry https://example.com/registry.json
# Set local registry (if no API/remote registry configured)
thv config set-registry /path/to/registry.json
# Check current registry configuration
thv config get-registry
# Remove custom registry (fall back to built-in)
thv config unset-registryImplementation: pkg/registry/factory.go, pkg/registry/provider.go, pkg/registry/provider_local.go, pkg/registry/provider_remote.go, pkg/registry/provider_api.go
Note: The registry API server is being moved to a separate project and will be maintained independently.
ToolHive includes a registry API server (thv-registry-api) for hosting custom MCP server registries.
Key capabilities:
- HTTP API for serving registry data
- File or Kubernetes ConfigMap storage
- Used by
MCPRegistryCRD in Kubernetes deployments
The registry API server provides HTTP endpoints for serving registry data to other components.
For Kubernetes deployments, registries managed via MCPRegistry CRD.
Implementation: cmd/thv-operator/api/v1alpha1/mcpregistry_types.go
apiVersion: toolhive.stacklok.dev/v1alpha1
kind: MCPRegistry
metadata:
name: company-registry
spec:
source:
type: git
git:
repository: https://github.com/company/mcp-registry
branch: main
path: registry.json
syncPolicy:
interval: 1hsource:
type: git
git:
repository: https://github.com/example/registry
branch: main
path: registry.jsonFeatures:
- Automatic sync from Git repository
- Branch or tag tracking
- Shallow clones for efficiency
Implementation: cmd/thv-operator/pkg/sources/git.go
source:
type: configmap
configMapRef:
name: mcp-registry-data
key: registry.jsonFeatures:
- Native Kubernetes resource
- Direct updates via kubectl
- No external dependencies
Implementation: cmd/thv-operator/pkg/sources/configmap.go
Automatic sync:
syncPolicy:
interval: 1hManual sync only:
Omit the syncPolicy field entirely. Manual sync can be triggered:
kubectl annotate mcpregistry company-registry \
toolhive.stacklok.dev/sync-trigger=trueImplementation: cmd/thv-operator/controllers/mcpregistry_controller.go
When apiService.enabled: true, operator creates:
- Deployment: Running
thv-registry-api - Service: Exposing API endpoints
- ConfigMap: Containing registry data
Access:
# Within cluster
curl http://company-registry-api.default.svc.cluster.local:8080/api/v1/registry
# Via port-forward
kubectl port-forward svc/company-registry-api 8080:8080
curl http://localhost:8080/api/v1/registryImplementation: cmd/thv-operator/pkg/registryapi/service.go
Status fields:
status:
phase: Ready
syncStatus:
phase: Complete
message: "Successfully synced registry"
lastSyncTime: "2025-10-13T12:00:00Z"
lastSyncHash: "abc123def456"
apiStatus:
phase: Ready
endpoint: "http://company-registry-api.default.svc.cluster.local:8080"Phases:
Pending- Initial stateSyncing- Fetching registry dataReady- Registry availableFailed- Sync failedTerminating- Registry being deleted
Implementation: cmd/thv-operator/pkg/mcpregistrystatus/
Registry data stored in ConfigMap:
Format:
apiVersion: v1
kind: ConfigMap
metadata:
name: company-registry-storage
ownerReferences:
- apiVersion: mcp.stacklok.com/v1alpha1
kind: MCPRegistry
name: company-registry
data:
registry.json: |
{ ... }
sync_metadata.json: |
{
"lastSyncTime": "2025-10-13T12:00:00Z",
"hash": "abc123"
}Owner references ensure automatic cleanup when MCPRegistry deleted.
Implementation: cmd/thv-operator/pkg/sources/storage_manager.go
Required fields:
image- Container image referencedescription- What the server doestransport- Communication protocoltier- Classification (Official, Partner, Community)
Optional fields:
target_port- Port for SSE/Streamable HTTPpermissions- Permission profileenv_vars- Environment variable definitionsargs- Default command argumentsdocker_tags- Available tagsprovenance- Supply chain metadatatools- List of tool namesmetadata- Stars, pulls, last updatedrepository_url- Source code URLtags- Categorization labels
Implementation: pkg/registry/types.go
Required fields:
url- Remote server endpointdescription- What the server doestransport- Must besseorstreamable-httptier- Classification
Optional fields:
headers- HTTP headers for authenticationoauth_config- OAuth/OIDC configurationenv_vars- Client environment variablestools- List of tool namesmetadata- Popularity metricsrepository_url- Documentation URLtags- Categorization labels
Implementation: pkg/registry/types.go
Structure:
{
"name": "data-pipeline",
"description": "Complete data processing pipeline",
"servers": {
"data-reader": { /* ImageMetadata */ },
"data-processor": { /* ImageMetadata */ }
},
"remote_servers": {
"data-warehouse": { /* RemoteServerMetadata */ }
}
}Use cases:
- Deploy related servers together
- Virtual MCP aggregation
- Organizational structure
Run all servers in group:
thv group run data-pipeline # assuming 'data-pipeline' is defined in your registryImplementation: pkg/registry/types.go
ToolHive supports Sigstore verification:
Provenance fields:
sigstore_url- Sigstore/Rekor instancerepository_uri- Source repositoryrepository_ref- Git ref (tag, commit)signer_identity- Who built the imagerunner_environment- Build environmentcert_issuer- Certificate authorityattestation- SLSA attestation data
Verification:
thv run weather-server --image-verification enabledImplementation:
pkg/registry/types.go- Provenance type definitionspkg/container/verifier/- Sigstore/cosign verification using sigstore-go librarypkg/runner/retriever/retriever.go- Image verification orchestration
Best practices:
- Pin image tags: Use specific versions, not
latest - Verify provenance: Check signer identity
- Review permissions: Audit network/file access
- Check repository: Review source code
- Monitor updates: Track registry updates
ToolHive will support the upstream MCP registry format:
Migration plan:
- Read both formats: ToolHive format + upstream format
- Deprecation period: Support both formats
Timeline: Planned for future release
Implementation: pkg/registry/ (converter to be added)
List servers:
thv registry listShow server info:
thv registry info <server-name>Implementation: cmd/thv/app/registry.go
Create registry:
kubectl apply -f mcpregistry.yamlCheck status:
kubectl get mcpregistry company-registry -o yamlTrigger manual sync:
kubectl annotate mcpregistry company-registry toolhive.stacklok.dev/sync-trigger=trueView registry data:
kubectl get configmap company-registry-storage -o jsonpath='{.data.registry\.json}' | jqImplementation: cmd/thv-operator/controllers/mcpregistry_controller.go
- Core Concepts - Registry concept
- Architecture Overview - Registry in platform
- Deployment Modes - Registry usage per mode
- Groups - Groups in registry
- Operator Architecture - MCPRegistry CRD