registryctl CLI
registryctl is the registry's CI tool: it lints manifests and toolspecs, compiles the signed index, and signs and verifies it. It's a thin shell over the schema/ Go package, so CI and the gateway share one validator — what passes locally passes in CI, byte for byte.
Running it
There's no install step. Run it with go run from the registry repo root:
cd gigmcp-registry
go run ./cmd/registryctl <subcommand> [args...]
On any error, registryctl prints registryctl: <error> to stderr and exits non-zero.
usage: registryctl lint|lint-toolspecs|build-index|sign|verify|keygen|build-args ...
:::note Flag placement
Subcommands that take a positional argument (lint, build-index) expect flags after the positional argument, Go-style: registryctl lint ./manifests -denylist denylist/exfil-domains.txt.
:::
lint
Validate every manifests/<name>/<version>.yaml under a directory.
registryctl lint <manifests-dir> [-denylist file]
| Flag | Default | Description |
|---|---|---|
-denylist | (none) | Path to an exfil-domain denylist file (one domain per line) |
Without -denylist, manifests are schema-validated only. With it, the full lint runs — including rejection of denylisted egress domains. Lint also enforces that each file's path matches the manifest's name/version (prevents PR sleight-of-hand) and fails if the directory contains no manifests.
go run ./cmd/registryctl lint ./manifests -denylist denylist/exfil-domains.txt
lint OK: 221 manifest(s)
This is exactly what the registry's lint CI runs on every PR.
lint-toolspecs
Validate toolspecs against their paired manifests.
registryctl lint-toolspecs <toolspecs-dir | toolspec.yaml> <manifests-dir>
Two modes:
- Directory mode (CI): validates every spec under the directory, checks each against its paired manifest, enforces the
toolspecs/<name>/<version>.yamlpath convention, and fails if any manifest withbuilder: toolpacklacks a spec (a toolpack image without a spec would have no/app/toolspec.yaml). - Single-file mode: validates one toolspec against just its paired manifest. Useful when authoring a spec without parsing siblings' work in progress.
# Full CI check
go run ./cmd/registryctl lint-toolspecs ./toolspecs ./manifests
# Just your spec
go run ./cmd/registryctl lint-toolspecs ./toolspecs/cloudflare/0.1.0.yaml ./manifests
lint-toolspecs OK: 1 toolspec
build-index
Compile all manifests into the registry index.
registryctl build-index <manifests-dir> [-out file]
| Flag | Default | Description |
|---|---|---|
-out | index.json | Output path |
go run ./cmd/registryctl build-index ./manifests -out index.json
Manifests are validated as they're loaded; the index records every server, its versions, and the latest version, stamped with the build time (RFC 3339, UTC).
sign
Sign an index file with the registry's ed25519 private key.
registryctl sign [-in file] [-out file]
| Flag | Default | Description |
|---|---|---|
-in | index.json | Index file to sign |
-out | index.json.sig | Signature output |
The private key is read from the GIG_SIGNING_KEY environment variable (64-byte ed25519 private key, hex-encoded). The command fails if it's unset.
GIG_SIGNING_KEY=<hex private key> \
go run ./cmd/registryctl sign -in index.json -out index.json.sig
In CI, the publish-index workflow runs this with the GIG_INDEX_SIGNING_KEY repo secret on every push to main.
verify
Verify an index signature against a public key — the same check the gateway performs before trusting any entry.
registryctl verify [-in file] [-sig file] -pub <hex>
| Flag | Default | Description |
|---|---|---|
-in | index.json | Index file |
-sig | index.json.sig | Signature file |
-pub | (none) | 32-byte ed25519 public key, hex |
go run ./cmd/registryctl verify -in index.json -sig index.json.sig -pub <hex public key>
signature OK
keygen
Generate a fresh ed25519 keypair, printed as hex.
registryctl keygen
go run ./cmd/registryctl keygen
public: 3f9a...
private: 8c41...
Key handling:
- Private key → GitHub Actions repo secret
GIG_INDEX_SIGNING_KEY(consumed by thepublish-indexworkflow). - Public key → gateway
GIG_REGISTRY_PUBKEYenvironment variable (or the baked-in default).
The private key signs the index every gateway trusts. Treat it like a root credential: generate it once, store it only as a CI secret, and never commit it.
build-args
Resolve a manifest into the build arguments the build-images CI workflow feeds to docker buildx build. You rarely need this directly, but it's useful for understanding exactly what gets built.
registryctl build-args <manifests/<name> | manifests/<name>/<version>.yaml>
- Given a directory (
manifests/<name>), it loads all versions and picks the latest. - Given a file, it parses and validates just that manifest.
Output is KEY=VALUE lines suitable for appending to $GITHUB_ENV:
go run ./cmd/registryctl build-args manifests/cloudflare/0.1.0.yaml
SOURCE_REPO=github.com/gigmcp/toolpack
SOURCE_TAG=v0.1.0
PACKAGE=.
BUILDER=toolpack
NAME=cloudflare
VERSION=0.1.0
PACKAGE defaults to . when source.package is empty, and BUILDER defaults to go-static when image.builder is omitted.
Where each command runs in CI
| Workflow | Trigger | Commands |
|---|---|---|
lint.yml | every PR and push to main | lint, lint-toolspecs, plus the aggregator-policy source check |
build-images.yml | manual dispatch by a maintainer | build-args, then docker buildx build --push, then prints the digest to pin |
publish-index.yml | push to main | lint, build-index, sign, then publishes index.json + index.json.sig as the rolling latest release |
See also
- Submitting a server — the contributor workflow these commands gate
- Registry overview — the trust chain end to end
- Manifest reference — what
lintenforces - Toolspecs — what
lint-toolspecsenforces