A standards-compliant MCP Registry API server for ToolHive
The ToolHive Registry API (thv-registry-api) implements the official Model Context Protocol (MCP) Registry API specification. It provides a standardized REST API for discovering and accessing MCP servers from multiple backend sources.
Features
Standards-compliant: Implements the official MCP Registry API specification
Multiple data sources: Git repositories, API endpoints, and local files
Automatic synchronization: Background sync with configurable intervals and retry logic
Container-ready: Designed for deployment in Kubernetes clusters
Flexible deployment: Works standalone or as part of ToolHive infrastructure
Production-ready: Built-in health checks, graceful shutdown, and sync status persistence
The server starts on port 8080 by default. Use --address :PORT to customize.
What happens when the server starts:
Loads configuration from the specified YAML file
Runs database migrations automatically (if database is configured)
Immediately fetches registry data from the configured source
Starts background sync coordinator for automatic updates
Serves MCP Registry API endpoints on the configured address
For detailed configuration options and examples, see the examples/README.md.
Available Commands
The thv-registry-api CLI provides the following commands:
# Start the API server
thv-registry-api serve --config config.yaml [--address :8080]
# Manually run database migrations
thv-registry-api migrate up --config config.yaml [--yes]
thv-registry-api migrate down --config config.yaml --num-steps N [--yes]
# Display version information
thv-registry-api version [--format json]
# Show help
thv-registry-api --help
thv-registry-api <command> --help
See the Database Migrations section for more details on using migration commands.
API Endpoints
The server implements the standard MCP Registry API:
GET /api/v0/servers - List all available MCP servers
GET /api/v0/servers/{name} - Get details for a specific server
GET /api/v0/deployed - List deployed server instances (Kubernetes only)
GET /api/v0/deployed/{name} - Get deployed instances of a specific server
See the MCP Registry API specification for full API details.
Note: The current implementation is not strictly compliant with the standard. The deviations will be fixed in the next iterations.
Configuration
All configuration is done via YAML files. The server requires a --config flag pointing to a YAML configuration file.
Use separate users for migrations (with elevated privileges) and normal operations (read-only or limited)
Never commit passwords directly in configuration files
Use password files with restricted permissions (e.g., chmod 400)
In Kubernetes, mount passwords from Secrets
Rotate passwords regularly
Connection Pooling
The server uses connection pooling for efficient database resource management:
MaxOpenConns: Limits concurrent database connections to prevent overwhelming the database
MaxIdleConns: Maintains idle connections for faster query execution
ConnMaxLifetime: Automatically closes and recreates connections to prevent connection leaks
Tune these values based on your workload:
High-traffic scenarios: Increase maxOpenConns and maxIdleConns
Resource-constrained environments: Decrease pool sizes
Long-running services: Set shorter connMaxLifetime (e.g., "1h")
Database Migrations
The server includes built-in database migration support to manage the database schema.
Automatic migrations on startup:
When you start the server with serve, database migrations run automatically if database configuration is present in your config file. This ensures your database schema is always up to date.
The only thing necessary is granting the role toolhive_registry_server to
the database user you want, for example
BEGIN;
DO $$
DECLARE
username TEXT := 'thvr_user';
password TEXT := 'custom-password';
BEGIN
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'toolhive_registry_server') THEN
CREATE ROLE toolhive_registry_server;
RAISE NOTICE 'Role toolhive_registry_server created';
END IF;
IF NOT EXISTS (SELECT FROM pg_user WHERE usename = username) THEN
EXECUTE format('CREATE USER %I WITH PASSWORD %L', username, password);
RAISE NOTICE 'User % created', username;
END IF;
EXECUTE format('GRANT toolhive_registry_server TO %I', username);
RAISE NOTICE 'Role toolhive_registry_server granted to %', username;
END
$$;
COMMIT;
To help with that, you can run the prime-db subcommand to configure a user with
the correct role as follows.
By default, the password is read from input, but it is also possible to pipe
it via shell via echo "mypassword" | thv-registry-api prime-db --config ....
Alternatively, the SQL script that would be executed can be printed to standard
output without touching the database.
# Build the image
task build-image
# Run with Git source
docker run -v $(pwd)/examples:/config \
ghcr.io/stacklok/toolhive/thv-registry-api:latest \
serve --config /config/config-git.yaml
# Run with file source (mount local registry file)
docker run -v $(pwd)/examples:/config \
-v /path/to/registry.json:/data/registry.json \
ghcr.io/stacklok/toolhive/thv-registry-api:latest \
serve --config /config/config-file.yaml
# Run with database password from environment variable
docker run -v $(pwd)/examples:/config \
-e THV_DATABASE_PASSWORD=your-password \
ghcr.io/stacklok/toolhive/thv-registry-api:latest \
serve --config /config/config-database-dev.yaml
Docker Compose
A complete Docker Compose setup is provided in the repository root that includes PostgreSQL and the API server with automatic migrations.
Quick start:
# Start all services (PostgreSQL + API with automatic migrations)
docker-compose up
# Run in detached mode
docker-compose up -d
# View logs
docker-compose logs -f registry-api
# Stop all services
docker-compose down
# Stop and remove volumes (WARNING: deletes database data)
docker-compose down -v
Architecture:
The docker-compose.yaml includes two services:
postgres - PostgreSQL 18 database server
registry-api - Main API server (runs migrations automatically on startup)
Service startup flow:
postgres (healthy) → registry-api (runs migrations, then starts)
Configuration:
Config file: examples/config-docker.yaml
Sample data: examples/registry-sample.json
Database passwords: Set via environment variables in docker-compose.yaml
THV_DATABASE_PASSWORD: Application user password
THV_DATABASE_MIGRATION_PASSWORD: Migration user password
The setup demonstrates:
Database-backed registry storage with separate users for migrations and operations
Automatic schema migrations on startup using elevated privileges
Normal operations using limited database privileges (principle of least privilege)