Skip to main content

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]
FlagDefaultDescription
-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>.yaml path convention, and fails if any manifest with builder: toolpack lacks 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]
FlagDefaultDescription
-outindex.jsonOutput 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]
FlagDefaultDescription
-inindex.jsonIndex file to sign
-outindex.json.sigSignature 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>
FlagDefaultDescription
-inindex.jsonIndex file
-sigindex.json.sigSignature 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 the publish-index workflow).
  • Public key → gateway GIG_REGISTRY_PUBKEY environment variable (or the baked-in default).
danger

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

WorkflowTriggerCommands
lint.ymlevery PR and push to mainlint, lint-toolspecs, plus the aggregator-policy source check
build-images.ymlmanual dispatch by a maintainerbuild-args, then docker buildx build --push, then prints the digest to pin
publish-index.ymlpush to mainlint, build-index, sign, then publishes index.json + index.json.sig as the rolling latest release

See also