Skip to main content

Submitting a Server

This page walks you through getting your MCP server into the Gig'MCP registry: from tagging a release in your own repo to seeing it published in the signed index.json that gateways install from.

:::note Aggregator policy Your server's source code lives in your repo. The registry holds only your manifest (and, rarely, a custom Dockerfile). CI rejects any PR that adds source code, examples/ directories, or .go files outside schema/ and cmd/. :::

How the pipeline works

  1. You tag a release of your server's source repo.
  2. You open a PR against the registry adding one file: manifests/<name>/<version>.yaml.
  3. CI lints the manifest with registryctl: schema validation, egress allowlist rules, denylisted exfil domains, and path/name consistency.
  4. A maintainer dispatches the build-images workflow, which builds your image from your tagged source using the builder named in your manifest and prints the resulting linux/amd64 image-manifest digest.
  5. You update your PR to pin that digest in image.digest.
  6. On owner review and merge, CI compiles all manifests into index.json, signs it (ed25519), and republishes it as the rolling latest release. Gateways verify the signature before trusting any entry.

What was approved in the PR is exactly what runs: the gateway pulls the image by digest, so the build can never drift from the reviewed manifest.

Step 1: Tag a release

Tag a release of your MCP server's source repo. The registry builds from that tag — never from a branch tip.

Step 2: Write the manifest

Add manifests/<name>/<version>.yaml. The path must match the manifest's name and version fields — CI rejects mismatches. See the manifest reference for every field; the essentials:

FieldRule
source.repo / source.tagThe tagged source to build from (your repo)
source.packageSubdirectory containing the server's main package; omit or leave blank to build from the repo root (.)
image.builderSelects the build recipe (images/<builder>/Dockerfile); omit or set to go-static for static Go binaries (the default)
image.entrypointMust be /app/server — every builder places the server there
entitlements.egressExact hostnames or *.suffix (≥2 labels) only
credentials[].injectheader + format for sealed tier, or env for entrusted tier
tools[].defaultMark the curated subset default: trueonly default tools are exposed to clients; a manifest with no default tools exposes nothing

A real example from the catalog (manifests/cloudflare/0.1.0.yaml, abridged):

schemaVersion: 1
name: cloudflare
version: 0.1.0
source:
repo: github.com/gigmcp/toolpack
tag: v0.1.0
image:
ref: ghcr.io/gigmcp/cloudflare-mcp
digest: sha256:0000000000000000000000000000000000000000000000000000000000000000
entrypoint: /app/server
builder: toolpack
tier: sealed
entitlements:
egress:
- api.cloudflare.com
credentials:
- id: cloudflare_token
type: api_key
provider: cloudflare
inject:
header: Authorization
format: "Bearer {token}"
tools:
- name: list_zones
default: true
- name: create_dns_record
default: false

:::warning Builder availability The node and python builders are prepared but not yet installable — they require the gateway's rootfs sandbox extension, which is designed but not yet shipped. Use go-static (the default) for static Go binaries, or toolpack for declarative HTTP tool mappings. See builders. :::

If your manifest uses builder: toolpack, you must also add a paired toolspecs/<name>/<version>.yaml — see toolspecs. CI runs registryctl lint-toolspecs and fails if a toolpack manifest has no spec.

If your server requires an unusual build (custom CGO flags, a non-Go toolchain, pre-built assets), you may add images/<name>/Dockerfile — but this is the exception, not the rule. The generic images/go-static/Dockerfile handles standard static Go servers.

Step 3: Open the PR

Open a PR adding your one manifest file (plus a toolspec or custom Dockerfile if applicable). Lint CI runs on every PR:

  • registryctl lint ./manifests -denylist denylist/exfil-domains.txt
  • registryctl lint-toolspecs ./toolspecs ./manifests
  • The aggregator-policy check (no server source in the registry)

You can run the same lint locally before pushing — see registryctl.

Step 4: Pin the digest

Use a placeholder digest (sha256:0000…) in your initial PR. A maintainer dispatches the build-images workflow with your server's name (version optional, defaults to latest). The workflow:

  1. Resolves build args from your manifest (registryctl build-args).
  2. Builds and pushes ghcr.io/gigmcp/<name>-mcp:<version> from your tagged source.
  3. Prints the linux/amd64 image-manifest digest to pin.

Update your PR to set image.digest to the printed value. Manifests with placeholder digests are not installable.

Step 5: Merge and publish

After owner review and merge, the publish-index workflow runs automatically on main: it lints, builds index.json from all manifests, signs it with the registry's ed25519 key, and republishes index.json + index.json.sig as the rolling latest release. No manual publish step.

:::note Version bumps force re-consent Manifest changes on a version bump force re-consent in every gateway that has the server installed. Users see exactly what changed before the new version can run. :::

Checklist

Before opening your PR:

  • Your server's source repo is public and has a tagged release.
  • Your PR adds exactly one file: manifests/<name>/<version>.yaml (plus a toolspec if builder: toolpack).
  • The file path matches the manifest's name and version fields.
  • image.entrypoint is /app/server.
  • Egress entries are exact hostnames or *.suffix with at least 2 labels — no broad wildcards.
  • Credentials match your tier: sealed → inject.header + format containing {token}; entrusted → inject.env only.
  • The tools you want exposed are marked default: true.
  • registryctl lint passes locally.
  • No source code, examples/, or stray .go files in the PR.

After CI builds your image:

  • image.digest is updated to the digest printed by build-images (no placeholder).

See also